diff --git a/.gitattributes b/.gitattributes index f41e91d8..7c0b591b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,7 +15,6 @@ LICENSE text *.conf text *.mimetypes text *.sh text eol=lf -components/openvino/eula.cfg text eol=lf *.avi binary *.bmp binary diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4b7ae8b1..32af66a2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,7 +23,7 @@ /datumaro/ @zhiltsov-max /cvat/apps/dataset_manager/ @zhiltsov-max -# Advanced components (e.g. OpenVINO) +# Advanced components (e.g. analytics) /components/ @azhavoro # Infrastructure diff --git a/.gitignore b/.gitignore index bc0260f9..cbe91f69 100644 --- a/.gitignore +++ b/.gitignore @@ -7,12 +7,12 @@ /.env /keys /logs -/components/openvino/*.tgz /profiles /ssh/* !/ssh/README.md node_modules /Mask_RCNN/ +/letsencrypt-webroot/ # Ignore temporary files docker-compose.override.yml diff --git a/.pylintrc b/.pylintrc index effa6735..677a3956 100644 --- a/.pylintrc +++ b/.pylintrc @@ -20,7 +20,7 @@ persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint_django # Use multiple processes to speed up Pylint. jobs=1 @@ -66,8 +66,8 @@ enable= E0001,E0100,E0101,E0102,E0103,E0104,E0105,E0106,E0107,E0110, W0122,W0124,W0150,W0199,W0221,W0222,W0233,W0404,W0410,W0601, W0602,W0604,W0611,W0612,W0622,W0623,W0702,W0705,W0711,W1300, W1301,W1302,W1303,,W1305,W1306,W1307 - R0102,R0201,R0202,R0203 - + R0102,R0202,R0203 + # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 00000000..4a37e44d --- /dev/null +++ b/.stylelintrc.json @@ -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" + ] +} diff --git a/.travis.yml b/.travis.yml index a8715db3..68533c0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,29 @@ language: python python: - "3.5" +cache: + npm: true + directories: + - ~/.cache + +addons: + apt: + packages: + - libgconf-2-4 + services: - docker env: - CONTAINER_COVERAGE_DATA_DIR="/coverage_data" HOST_COVERAGE_DATA_DIR="${TRAVIS_BUILD_DIR}" + DJANGO_SU_NAME="admin" + DJANGO_SU_EMAIL="admin@localhost.company" + DJANGO_SU_PASSWORD="12qwaszx" + NODE_VERSION="12" + +before_install: + - nvm install ${NODE_VERSION} before_script: - docker-compose -f docker-compose.yml -f docker-compose.ci.yml build @@ -20,6 +37,13 @@ script: # FIXME: Git package and application name conflict in PATH and try to leave only one python test execution - docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps && coverage run -a manage.py test --pattern="_test*.py" cvat/apps/dataset_manager/tests cvat/apps/engine/tests utils/cli && coverage run -a manage.py test datumaro/ && mv .coverage ${CONTAINER_COVERAGE_DATA_DIR}' - docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm install && cd ../cvat-core && npm install && npm run test && coveralls-lcov -v -n ./reports/coverage/lcov.info > ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json' + # Up all containers + - docker-compose up -d + # Create superuser + - docker exec -it cvat bash -ic "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" + # Install Cypress and run tests + - cd ./tests && npm install + - $(npm bin)/cypress run --headless --browser chrome && cd .. after_success: # https://coveralls-python.readthedocs.io/en/latest/usage/multilang.html diff --git a/.vscode/launch.json b/.vscode/launch.json index 1fefab7f..48bc4583 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -27,7 +27,7 @@ "request": "launch", "stopOnEntry": false, "justMyCode": false, - "pythonPath": "${config:python.pythonPath}", + "pythonPath": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "args": [ "runserver", @@ -58,7 +58,7 @@ "request": "launch", "stopOnEntry": false, "justMyCode": false, - "pythonPath": "${config:python.pythonPath}", + "pythonPath": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "args": [ "rqworker", @@ -77,7 +77,7 @@ "request": "launch", "stopOnEntry": false, "justMyCode": false, - "pythonPath": "${config:python.pythonPath}", + "pythonPath": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "args": [ "rqscheduler", @@ -93,7 +93,7 @@ "request": "launch", "justMyCode": false, "stopOnEntry": false, - "pythonPath": "${config:python.pythonPath}", + "pythonPath":"${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "args": [ "rqworker", @@ -112,7 +112,7 @@ "request": "launch", "justMyCode": false, "stopOnEntry": false, - "pythonPath": "${config:python.pythonPath}", + "pythonPath": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "args": [ "update_git_states" @@ -128,7 +128,7 @@ "request": "launch", "justMyCode": false, "stopOnEntry": false, - "pythonPath": "${config:python.pythonPath}", + "pythonPath": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "args": [ "migrate" @@ -144,7 +144,7 @@ "request": "launch", "justMyCode": false, "stopOnEntry": false, - "pythonPath": "${config:python.pythonPath}", + "pythonPath": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "args": [ "test", diff --git a/CHANGELOG.md b/CHANGELOG.md index 600c9676..eb1d1dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/), 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 () +- [Datumaro] Added model info and source info commands () +- [Datumaro] Dataset statistics () +- Ability to change label color in tasks and predefined labels () +- [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695) +- Ability to configure email verification for new users () +- Link to django admin page from UI () +- Notification message when users use wrong browser () + +### Changed +- Shape coordinates are rounded to 2 digits in dumped annotations () +- COCO format does not produce polygon points for bbox annotations () + +### Fixed +- Issue loading openvino models for semi-automatic and automatic annotation () +- Basic functions of CVAT works without activated nuclio dashboard +- Fixed a case in which exported masks could have wrong color order () +- Fixed error with creating task with labels with the same name () +- Django RQ dashboard view () + +## [1.1.0-beta] - 2020-08-03 +### Added +- DL models as serverless functions () +- Source type support for tags, shapes and tracks () +- Source type support for CVAT Dumper/Loader () +- Intelligent polygon editing () +- Support creating multiple jobs for each task through python cli (https://github.com/opencv/cvat/pull/1950) +- python cli over https () +- Error message when plugins weren't able to initialize instead of infinite loading () +- Ability to change user password () + +### Changed +- Smaller object details () +- `COCO` format does not convert bboxes to polygons on export () +- 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 () +- Files and folders under share path are now alphabetically sorted + +### Removed +- Removed OpenVINO and CUDA components because they are not necessary anymore () +- Removed the old UI code () + +### Fixed +- Some objects aren't shown on canvas sometimes. For example after propagation on of objects is invisible () +- CVAT doesn't offer to restore state after an error () +- Cannot read property 'shapeType' of undefined because of zOrder related issues () +- Cannot read property 'pinned' of undefined because of zOrder related issues () +- Do not iterate over hidden objects in aam (which are invisible because of zOrder) () +- Cursor position is reset after changing a text field () +- Hidden points and cuboids can be selected to be groupped () +- `outside` annotations should not be in exported images () +- `CVAT for video format` import error with interpolation () +- `Image compression` definition mismatch () +- Points are dublicated during polygon interpolation sometimes () +- When redraw a shape with activated autobordering, previous points are visible () +- No mapping between side object element and context menu in some attributes () +- Interpolated shapes exported as `keyframe = True` () +- Stylelint filetype scans () +- Fixed toolip closing issue () +- Clearing frame cache when close a task () +- Increase rate of throttling policy for unauthenticated users () + +## [1.1.0-alpha] - 2020-06-30 +### Added +- Throttling policy for unauthenticated users () +- Added default label color table for mask export () +- Added environment variables for Redis and Postgres hosts for Kubernetes deployment support () +- Added visual identification for unavailable formats () +- Shortcut to change color of an activated shape in new UI (Enter) () +- Shortcut to switch split mode () +- Built-in search for labels when create an object or change a label () +- Better validation of labels and attributes in raw viewer () +- ClamAV antivirus integration () +- Added canvas background color selector () +- SCSS files linting with Stylelint tool () +- 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 () +- Ability to redraw shape from scratch (Shift + N) for an activated shape () +- Highlights for the first point of a polygon/polyline and direction () +- Ability to change orientation for poylgons/polylines in context menu () +- Ability to set the first point for polygons in points context menu () +- Added new tag annotation workspace () +- Appearance block in attribute annotation mode () +- Keyframe navigations and some switchers in attribute annotation mode () +- [Datumaro] Added `convert` command to convert datasets directly () +- [Datumaro] Added an option to specify image extension when exporting datasets () +- [Datumaro] Added image copying when exporting datasets, if possible () + +### Changed +- Removed information about e-mail from the basic user information () +- Update https install manual. Makes it easier and more robust. Includes automatic renewing of lets encrypt certificates. +- Settings page move to the modal. () +- Implemented import and export of annotations with relative image paths () +- Using only single click to start editing or remove a point () +- 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 () +- [Datumaro] Annotation-less files are not generated anymore in COCO format, unless tasks explicitly requested () + +### Fixed +- Problem with exported frame stepped image task () +- Fixed dataset filter item representation for imageless dataset items () +- Fixed interpreter crash when trying to import `tensorflow` with no AVX instructions available () +- Kibana wrong working time calculation with new annotation UI use () +- Wrong rexex for account name validation () +- Wrong description on register view for the username field () +- Wrong resolution for resizing a shape () +- React warning because of not unique keys in labels viewer () +- Fixed issue tracker () +- Fixed canvas fit after sidebar open/close event () +- A couple of exceptions in AAM related with early object activation () +- Propagation from the latest frame () +- Number attribute value validation (didn't work well with floats) () +- Logout doesn't work () +- Annotations aren't updated after reopening a task () +- Labels aren't updated after reopening a task () +- Canvas isn't fitted after collapsing side panel in attribute annotation mode () +- Error when interpolating polygons () + +### Security +- SQL injection in Django `CVE-2020-9402` () + ## [1.0.0] - 2020-05-29 ### Added - cvat-ui: cookie policy drawer for login page () @@ -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 () - Unable to upload video with uneven size () - Fixed an issue with `z_order` having no effect on segmentations () + +### Security - Permission group whitelist check for analytics view () ## [1.0.0-beta.2] - 2020-04-30 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b02c62c..08c3cfd0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Next steps should work on clear Ubuntu 18.04. - Install necessary dependencies: ```sh - $ sudo apt-get update && sudo apt-get --no-install-recommends install -y ffmpeg build-essential curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev + sudo apt-get update && sudo apt-get --no-install-recommends install -y ffmpeg build-essential curl redis-server python3-dev python3-pip python3-venv python3-tk libldap2-dev libsasl2-dev ``` Also please make sure that you have installed ffmpeg with all necessary libav* libraries and pkg-config package. ```sh @@ -60,6 +60,7 @@ for development - Install npm packages for UI and start UI debug server (run the following command from CVAT root directory): ```sh + npm install && \ cd cvat-core && npm install && \ cd ../cvat-ui && npm install && npm start ``` @@ -69,10 +70,11 @@ for development cd .. && source .env/bin/activate && code ``` -- Install followig vscode extensions: +- Install following VS Code extensions: - [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + - [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - [vscode-remark-lint](https://marketplace.visualstudio.com/items?itemName=drewbourne.vscode-remark-lint) - [licenser](https://marketplace.visualstudio.com/items?itemName=ymotongpoo.licenser) - [Trailing Spaces](https://marketplace.visualstudio.com/items?itemName=shardulm94.trailing-spaces) @@ -83,87 +85,177 @@ for development You have done! Now it is possible to insert breakpoints and debug server and client of the tool. -## How to setup additional components in development environment +### Note for Windows users -### Automatic annotation -- Install OpenVINO on your host machine according to instructions from -[OpenVINO website](https://docs.openvinotoolkit.org/latest/index.html) -- Add some environment variables (copy code below to the end of ``.env/bin/activate`` file): -```sh - source /opt/intel/openvino/bin/setupvars.sh +You develop CVAT under WSL (Windows subsystem for Linux) following next steps. - export OPENVINO_TOOLKIT="yes" - export IE_PLUGINS_PATH="/opt/intel/openvino/deployment_tools/inference_engine/lib/intel64" - export OpenCV_DIR="/usr/local/lib/cmake/opencv4" - export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/opt/intel/openvino/inference_engine/lib/intel64" -``` +- Install WSL using [this guide](https://docs.microsoft.com/ru-ru/windows/wsl/install-win10). -Notice 1: be sure that these paths actually exist. Some of them can differ in different OpenVINO versions. +- Following this guide install Ubuntu 18.04 Linux distribution for WSL. -Notice 2: you need to deactivate, activate again and restart vs code -to changes in ``.env/bin/activate`` file are active. +- Run Ubuntu using start menu link or execute next command + ```powershell + wsl -d Ubuntu-18.04 + ``` -### ReID algorithm -- Perform all steps in the automatic annotation section -- Download ReID model and save it somewhere: -```sh - curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -o 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.bin +- Run all commands from this isntallation guide in WSL Ubuntu shell. +## Setup additional components in development environment + +### DL models as serverless functions + +Install [nuclio platform](https://github.com/nuclio/nuclio): +- You have to install `nuctl` command line tool to build and deploy serverless +functions. Download [the latest release]( +https://github.com/nuclio/nuclio/blob/development/docs/reference/nuctl/nuctl.md#download). +- The simplest way to explore Nuclio is to run its graphical user interface (GUI) +of the Nuclio dashboard. All you need in order to run the dashboard is Docker. See +[nuclio documentation](https://github.com/nuclio/nuclio#quick-start-steps) +for more details. +- Create `cvat` project inside nuclio dashboard where you will deploy new +serverless functions and deploy a couple of DL models. + +```bash +nuctl create project cvat ``` -- Add next line to ``.env/bin/activate``: -```sh - export REID_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files + +```bash +nuctl deploy --project-name cvat \ + --path serverless/openvino/dextr/nuclio \ + --volume `pwd`/serverless/openvino/common:/opt/nuclio/common \ + --platform local ``` -### Deep Extreme Cut -- Perform all steps in the automatic annotation section -- Download Deep Extreme Cut model, unpack it, and save somewhere: -```sh -curl https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -o dextr.zip -unzip dextr.zip +
+ +```bash +20.07.17 12:02:23.247 nuctl (I) Deploying function {"name": ""} +20.07.17 12:02:23.248 nuctl (I) Building {"versionInfo": "Label: 1.4.8, Git commit: 238d4539ac7783896d6c414535d0462b5f4cbcf1, OS: darwin, Arch: amd64, Go version: go1.14.3", "name": ""} +20.07.17 12:02:23.447 nuctl (I) Cleaning up before deployment +20.07.17 12:02:23.535 nuctl (I) Function already exists, deleting +20.07.17 12:02:25.877 nuctl (I) Staging files and preparing base images +20.07.17 12:02:25.891 nuctl (I) Building processor image {"imageName": "cvat/openvino.dextr:latest"} +20.07.17 12:02:25.891 nuctl.platform.docker (I) Pulling image {"imageName": "quay.io/nuclio/handler-builder-python-onbuild:1.4.8-amd64"} +20.07.17 12:02:29.270 nuctl.platform.docker (I) Pulling image {"imageName": "quay.io/nuclio/uhttpc:0.0.1-amd64"} +20.07.17 12:02:33.208 nuctl.platform (I) Building docker image {"image": "cvat/openvino.dextr:latest"} +20.07.17 12:02:34.464 nuctl.platform (I) Pushing docker image into registry {"image": "cvat/openvino.dextr:latest", "registry": ""} +20.07.17 12:02:34.464 nuctl.platform (I) Docker image was successfully built and pushed into docker registry {"image": "cvat/openvino.dextr:latest"} +20.07.17 12:02:34.464 nuctl (I) Build complete {"result": {"Image":"cvat/openvino.dextr:latest","UpdatedFunctionConfig":{"metadata":{"name":"openvino.dextr","namespace":"nuclio","labels":{"nuclio.io/project-name":"cvat"},"annotations":{"framework":"openvino","spec":"","type":"interactor"}},"spec":{"description":"Deep Extreme Cut","handler":"main:handler","runtime":"python:3.6","env":[{"name":"NUCLIO_PYTHON_EXE_PATH","value":"/opt/nuclio/python3"}],"resources":{},"image":"cvat/openvino.dextr:latest","targetCPU":75,"triggers":{"myHttpTrigger":{"class":"","kind":"http","name":"","maxWorkers":2,"workerAvailabilityTimeoutMilliseconds":10000,"attributes":{"maxRequestBodySize":33554432}}},"volumes":[{"volume":{"name":"volume-1","hostPath":{"path":"/Users/nmanovic/Workspace/cvat/serverless/openvino/common"}},"volumeMount":{"name":"volume-1","mountPath":"/opt/nuclio/common"}}],"build":{"image":"cvat/openvino.dextr","baseImage":"openvino/ubuntu18_runtime:2020.2","directives":{"postCopy":[{"kind":"RUN","value":"curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip"},{"kind":"RUN","value":"unzip dextr_model_v1.zip"},{"kind":"RUN","value":"pip3 install Pillow"},{"kind":"USER","value":"openvino"}],"preCopy":[{"kind":"USER","value":"root"},{"kind":"WORKDIR","value":"/opt/nuclio"},{"kind":"RUN","value":"ln -s /usr/bin/pip3 /usr/bin/pip"}]},"codeEntryType":"image"},"platform":{},"readinessTimeoutSeconds":60,"eventTimeout":"30s"}}}} +20.07.17 12:02:35.012 nuctl.platform (I) Waiting for function to be ready {"timeout": 60} +20.07.17 12:02:37.448 nuctl (I) Function deploy complete {"httpPort": 55274} ``` -- Add next lines to ``.env/bin/activate``: -```sh - export WITH_DEXTR="yes" - export DEXTR_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files + +
+ +```bash +nuctl deploy --project-name cvat \ + --path serverless/openvino/omz/public/yolo-v3-tf/nuclio \ + --volume `pwd`/serverless/openvino/common:/opt/nuclio/common \ + --platform local ``` -### Tensorflow RCNN -- Download RCNN model, unpack it, and save it somewhere: -```sh -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 +
+ +```bash +20.07.17 12:05:23.377 nuctl (I) Deploying function {"name": ""} +20.07.17 12:05:23.378 nuctl (I) Building {"versionInfo": "Label: 1.4.8, Git commit: 238d4539ac7783896d6c414535d0462b5f4cbcf1, OS: darwin, Arch: amd64, Go version: go1.14.3", "name": ""} +20.07.17 12:05:23.590 nuctl (I) Cleaning up before deployment +20.07.17 12:05:23.694 nuctl (I) Function already exists, deleting +20.07.17 12:05:24.271 nuctl (I) Staging files and preparing base images +20.07.17 12:05:24.274 nuctl (I) Building processor image {"imageName": "cvat/openvino.omz.public.yolo-v3-tf:latest"} +20.07.17 12:05:24.274 nuctl.platform.docker (I) Pulling image {"imageName": "quay.io/nuclio/handler-builder-python-onbuild:1.4.8-amd64"} +20.07.17 12:05:27.432 nuctl.platform.docker (I) Pulling image {"imageName": "quay.io/nuclio/uhttpc:0.0.1-amd64"} +20.07.17 12:05:31.462 nuctl.platform (I) Building docker image {"image": "cvat/openvino.omz.public.yolo-v3-tf:latest"} +20.07.17 12:05:32.798 nuctl.platform (I) Pushing docker image into registry {"image": "cvat/openvino.omz.public.yolo-v3-tf:latest", "registry": ""} +20.07.17 12:05:32.798 nuctl.platform (I) Docker image was successfully built and pushed into docker registry {"image": "cvat/openvino.omz.public.yolo-v3-tf:latest"} +20.07.17 12:05:32.798 nuctl (I) Build complete {"result": {"Image":"cvat/openvino.omz.public.yolo-v3-tf:latest","UpdatedFunctionConfig":{"metadata":{"name":"openvino.omz.public.yolo-v3-tf","namespace":"nuclio","labels":{"nuclio.io/project-name":"cvat"},"annotations":{"framework":"openvino","name":"YOLO v3","spec":"[\n { \"id\": 0, \"name\": \"person\" },\n { \"id\": 1, \"name\": \"bicycle\" },\n { \"id\": 2, \"name\": \"car\" },\n { \"id\": 3, \"name\": \"motorbike\" },\n { \"id\": 4, \"name\": \"aeroplane\" },\n { \"id\": 5, \"name\": \"bus\" },\n { \"id\": 6, \"name\": \"train\" },\n { \"id\": 7, \"name\": \"truck\" },\n { \"id\": 8, \"name\": \"boat\" },\n { \"id\": 9, \"name\": \"traffic light\" },\n { \"id\": 10, \"name\": \"fire hydrant\" },\n { \"id\": 11, \"name\": \"stop sign\" },\n { \"id\": 12, \"name\": \"parking meter\" },\n { \"id\": 13, \"name\": \"bench\" },\n { \"id\": 14, \"name\": \"bird\" },\n { \"id\": 15, \"name\": \"cat\" },\n { \"id\": 16, \"name\": \"dog\" },\n { \"id\": 17, \"name\": \"horse\" },\n { \"id\": 18, \"name\": \"sheep\" },\n { \"id\": 19, \"name\": \"cow\" },\n { \"id\": 20, \"name\": \"elephant\" },\n { \"id\": 21, \"name\": \"bear\" },\n { \"id\": 22, \"name\": \"zebra\" },\n { \"id\": 23, \"name\": \"giraffe\" },\n { \"id\": 24, \"name\": \"backpack\" },\n { \"id\": 25, \"name\": \"umbrella\" },\n { \"id\": 26, \"name\": \"handbag\" },\n { \"id\": 27, \"name\": \"tie\" },\n { \"id\": 28, \"name\": \"suitcase\" },\n { \"id\": 29, \"name\": \"frisbee\" },\n { \"id\": 30, \"name\": \"skis\" },\n { \"id\": 31, \"name\": \"snowboard\" },\n { \"id\": 32, \"name\": \"sports ball\" },\n { \"id\": 33, \"name\": \"kite\" },\n { \"id\": 34, \"name\": \"baseball bat\" },\n { \"id\": 35, \"name\": \"baseball glove\" },\n { \"id\": 36, \"name\": \"skateboard\" },\n { \"id\": 37, \"name\": \"surfboard\" },\n { \"id\": 38, \"name\": \"tennis racket\" },\n { \"id\": 39, \"name\": \"bottle\" },\n { \"id\": 40, \"name\": \"wine glass\" },\n { \"id\": 41, \"name\": \"cup\" },\n { \"id\": 42, \"name\": \"fork\" },\n { \"id\": 43, \"name\": \"knife\" },\n { \"id\": 44, \"name\": \"spoon\" },\n { \"id\": 45, \"name\": \"bowl\" },\n { \"id\": 46, \"name\": \"banana\" },\n { \"id\": 47, \"name\": \"apple\" },\n { \"id\": 48, \"name\": \"sandwich\" },\n { \"id\": 49, \"name\": \"orange\" },\n { \"id\": 50, \"name\": \"broccoli\" },\n { \"id\": 51, \"name\": \"carrot\" },\n { \"id\": 52, \"name\": \"hot dog\" },\n { \"id\": 53, \"name\": \"pizza\" },\n { \"id\": 54, \"name\": \"donut\" },\n { \"id\": 55, \"name\": \"cake\" },\n { \"id\": 56, \"name\": \"chair\" },\n { \"id\": 57, \"name\": \"sofa\" },\n { \"id\": 58, \"name\": \"pottedplant\" },\n { \"id\": 59, \"name\": \"bed\" },\n { \"id\": 60, \"name\": \"diningtable\" },\n { \"id\": 61, \"name\": \"toilet\" },\n { \"id\": 62, \"name\": \"tvmonitor\" },\n { \"id\": 63, \"name\": \"laptop\" },\n { \"id\": 64, \"name\": \"mouse\" },\n { \"id\": 65, \"name\": \"remote\" },\n { \"id\": 66, \"name\": \"keyboard\" },\n { \"id\": 67, \"name\": \"cell phone\" },\n { \"id\": 68, \"name\": \"microwave\" },\n { \"id\": 69, \"name\": \"oven\" },\n { \"id\": 70, \"name\": \"toaster\" },\n { \"id\": 71, \"name\": \"sink\" },\n { \"id\": 72, \"name\": \"refrigerator\" },\n { \"id\": 73, \"name\": \"book\" },\n { \"id\": 74, \"name\": \"clock\" },\n { \"id\": 75, \"name\": \"vase\" },\n { \"id\": 76, \"name\": \"scissors\" },\n { \"id\": 77, \"name\": \"teddy bear\" },\n { \"id\": 78, \"name\": \"hair drier\" },\n { \"id\": 79, \"name\": \"toothbrush\" }\n]\n","type":"detector"}},"spec":{"description":"YOLO v3 via Intel OpenVINO","handler":"main:handler","runtime":"python:3.6","env":[{"name":"NUCLIO_PYTHON_EXE_PATH","value":"/opt/nuclio/common/python3"}],"resources":{},"image":"cvat/openvino.omz.public.yolo-v3-tf:latest","targetCPU":75,"triggers":{"myHttpTrigger":{"class":"","kind":"http","name":"","maxWorkers":2,"workerAvailabilityTimeoutMilliseconds":10000,"attributes":{"maxRequestBodySize":33554432}}},"volumes":[{"volume":{"name":"volume-1","hostPath":{"path":"/Users/nmanovic/Workspace/cvat/serverless/openvino/common"}},"volumeMount":{"name":"volume-1","mountPath":"/opt/nuclio/common"}}],"build":{"image":"cvat/openvino.omz.public.yolo-v3-tf","baseImage":"openvino/ubuntu18_dev:2020.2","directives":{"postCopy":[{"kind":"USER","value":"openvino"}],"preCopy":[{"kind":"USER","value":"root"},{"kind":"WORKDIR","value":"/opt/nuclio"},{"kind":"RUN","value":"ln -s /usr/bin/pip3 /usr/bin/pip"},{"kind":"RUN","value":"/opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name yolo-v3-tf -o /opt/nuclio/open_model_zoo"},{"kind":"RUN","value":"/opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name yolo-v3-tf --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo"}]},"codeEntryType":"image"},"platform":{},"readinessTimeoutSeconds":60,"eventTimeout":"30s"}}}} +20.07.17 12:05:33.285 nuctl.platform (I) Waiting for function to be ready {"timeout": 60} +20.07.17 12:05:35.452 nuctl (I) Function deploy complete {"httpPort": 57308} ``` -- Add next lines to ``.env/bin/activate``: -```sh - export TF_ANNOTATION="yes" - export TF_ANNOTATION_MODEL_PATH="/path/to/the/model/graph" # truncate .pb extension + +
+ +- Display a list of running serverless functions using `nuctl` command or see them +in nuclio dashboard: + +```bash +nuctl get function ``` -### Tensorflow Mask RCNN -- Download Mask RCNN model, and save it somewhere: -```sh -curl https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5 -o mask_rcnn_coco.h5 +
+ +```bash + NAMESPACE | NAME | PROJECT | STATE | NODE PORT | REPLICAS + nuclio | openvino.dextr | cvat | ready | 55274 | 1/1 + nuclio | openvino.omz.public.yolo-v3-tf | cvat | ready | 57308 | 1/1 +``` + +
+ +- Test your deployed DL model as a serverless function. The command below +should work on Linux and Mac OS. + +```bash +image=$(curl https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png --output - | base64 | tr -d '\n') +cat << EOF > /tmp/input.json +{"image": "$image"} +EOF +cat /tmp/input.json | nuctl invoke openvino.omz.public.yolo-v3-tf -c 'application/json' +``` + +
+ +```bash +20.07.17 12:07:44.519 nuctl.platform.invoker (I) Executing function {"method": "POST", "url": "http://:57308", "headers": {"Content-Type":["application/json"],"X-Nuclio-Log-Level":["info"],"X-Nuclio-Target":["openvino.omz.public.yolo-v3-tf"]}} +20.07.17 12:07:45.275 nuctl.platform.invoker (I) Got response {"status": "200 OK"} +20.07.17 12:07:45.275 nuctl (I) >>> Start of function logs +20.07.17 12:07:45.275 ino.omz.public.yolo-v3-tf (I) Run yolo-v3-tf model {"worker_id": "0", "time": 1594976864570.9353} +20.07.17 12:07:45.275 nuctl (I) <<< End of function logs + +> Response headers: +Date = Fri, 17 Jul 2020 09:07:45 GMT +Content-Type = application/json +Content-Length = 100 +Server = nuclio + +> Response body: +[ + { + "confidence": "0.9992254", + "label": "person", + "points": [ + 39, + 124, + 408, + 512 + ], + "type": "rectangle" + } +] ``` -- Add next lines to ``.env/bin/activate``: + +### Run Cypress tests +- Install Сypress as described in the [documentation](https://docs.cypress.io/guides/getting-started/installing-cypress.html). +- Run cypress tests: ```sh - export AUTO_SEGMENTATION="yes" - export AUTO_SEGMENTATION_PATH="/path/to/dir" # dir must contain mask_rcnn_coco.h5 file + cd /tests + /node_modules/.bin/cypress run --headless --browser chrome ``` +For more information, see the [documentation](https://docs.cypress.io/). ## JavaScript/Typescript coding style We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for JavaScript code with a -litle exception - we prefere 4 spaces for indentation of nested blocks and statements. +little exception - we prefer 4 spaces for indentation of nested blocks and statements. ## Branching model The project uses [a successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model). Thus it has a couple of branches. Some of them are described below: -- `origin/master` to be the main branch where the source code of -HEAD always reflects a production-ready state. -- `origin/develop` to be the main branch where the source code of +- `origin/master` to be the main branch where the source code of +HEAD always reflects a production-ready state + +- `origin/develop` to be the main branch where the source code of HEAD always reflects a state with the latest delivered development changes for the next release. Some would call this the “integration branch”. @@ -173,13 +265,13 @@ The issue tracker is the preferred channel for [bug reports](#bugs), [features requests](#features) and [submitting pull requests](#pull-requests), but please respect the following restrictions: -* Please **do not** use the issue tracker for personal support requests (use +- Please **do not** use the issue tracker for personal support requests (use [Stack Overflow](http://stackoverflow.com)). -* Please **do not** derail or troll issues. Keep the discussion on topic and +- Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others. - + ## Bug reports A bug is a _demonstrable problem_ that is caused by the code in the repository. @@ -187,13 +279,13 @@ Good bug reports are extremely helpful - thank you! Guidelines for bug reports: -1. **Use the GitHub issue search** — check if the issue has already been +1. **Use the GitHub issue search** — check if the issue has already been reported. -2. **Check if the issue has been fixed** — try to reproduce it using the +1. **Check if the issue has been fixed** — try to reproduce it using the latest `develop` branch in the repository. -3. **Isolate the problem** — ideally create a reduced test case. +1. **Isolate the problem** — ideally create a reduced test case. A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is @@ -209,9 +301,8 @@ Example: > suitable, include the steps required to reproduce the bug. > > 1. This is the first step -> 2. This is the second step -> 3. Further steps, etc. -> +> 1. This is the second step +> 1. Further steps, etc. > > Any other information you want to share that is relevant to the issue being > reported. This might include the lines of code that you have identified as @@ -222,7 +313,7 @@ Example: ## Feature requests Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong +fits with the scope and aims of the project. It's up to _you_ to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. @@ -244,51 +335,51 @@ accurate comments, etc.) and any other requirements (such as test coverage). Follow this process if you'd like your work considered for inclusion in the project: -1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, - and configure the remotes: +1. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the project, clone your fork, + and configure the remotes: - ```bash - # Clone your fork of the repo into the current directory - git clone https://github.com// - # Navigate to the newly cloned directory - cd - # Assign the original repo to a remote called "upstream" - git remote add upstream https://github.com// - ``` + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com// + # Navigate to the newly cloned directory + cd + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com// + ``` -2. If you cloned a while ago, get the latest changes from upstream: +1. If you cloned a while ago, get the latest changes from upstream: - ```bash - git checkout - git pull upstream - ``` + ```bash + git checkout + git pull upstream + ``` -3. Create a new topic branch (off the main project development branch) to - contain your feature, change, or fix: +1. Create a new topic branch (off the main project development branch) to + contain your feature, change, or fix: - ```bash - git checkout -b - ``` + ```bash + git checkout -b + ``` -4. Commit your changes in logical chunks. Please adhere to these [git commit +1. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's - [interactive rebase](https://help.github.com/articles/interactive-rebase) + [interactive rebase](https://docs.github.com/en/github/using-git/about-git-rebase) feature to tidy up your commits before making them public. -5. Locally merge (or rebase) the upstream development branch into your topic branch: +1. Locally merge (or rebase) the upstream development branch into your topic branch: - ```bash - git pull [--rebase] upstream - ``` + ```bash + git pull [--rebase] upstream + ``` -6. Push your topic branch up to your fork: +1. Push your topic branch up to your fork: - ```bash - git push origin - ``` + ```bash + git push origin + ``` -7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) +1. [Open a Pull Request](hhttps://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) with a clear title and description. **IMPORTANT**: By submitting a patch, you agree to allow the project owner to diff --git a/Dockerfile b/Dockerfile index 9c00bf8f..07060f19 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,6 @@ RUN apt-get update && \ libavfilter-dev \ libavformat-dev \ libavutil-dev \ - libldap2-dev \ libswresample-dev \ libswscale-dev \ libldap2-dev \ @@ -56,7 +55,7 @@ RUN apt-get update && \ curl && \ 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 && \ - 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 && \ dpkg-reconfigure -f noninteractive tzdata && \ 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 -# OpenVINO toolkit support -ARG OPENVINO_TOOLKIT -ENV OPENVINO_TOOLKIT=${OPENVINO_TOOLKIT} -ENV REID_MODEL_DIR=${HOME}/reid -RUN if [ "$OPENVINO_TOOLKIT" = "yes" ]; then \ - /tmp/components/openvino/install.sh && \ - mkdir ${REID_MODEL_DIR} && \ - curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -o reid/reid.xml && \ - curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -o reid/reid.bin; \ - fi - -# Tensorflow annotation support -ARG TF_ANNOTATION -ENV TF_ANNOTATION=${TF_ANNOTATION} -ENV TF_ANNOTATION_MODEL_PATH=${HOME}/rcnn/inference_graph -RUN if [ "$TF_ANNOTATION" = "yes" ]; then \ - bash -i /tmp/components/tf_annotation/install.sh; \ - fi - -# Auto segmentation support. by Mohammad -ARG AUTO_SEGMENTATION -ENV AUTO_SEGMENTATION=${AUTO_SEGMENTATION} -ENV AUTO_SEGMENTATION_PATH=${HOME}/Mask_RCNN -RUN if [ "$AUTO_SEGMENTATION" = "yes" ]; then \ - bash -i /tmp/components/auto_segmentation/install.sh; \ - fi - # Install and initialize CVAT, copy all necessary files COPY cvat/requirements/ /tmp/requirements/ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ @@ -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 RUN python3 -m pip install --no-cache-dir pycocotools==2.0.0 - -# CUDA support -ARG CUDA_SUPPORT -ENV CUDA_SUPPORT=${CUDA_SUPPORT} -RUN if [ "$CUDA_SUPPORT" = "yes" ]; then \ - /tmp/components/cuda/install.sh; \ - fi - -# TODO: CHANGE URL -ARG WITH_DEXTR -ENV WITH_DEXTR=${WITH_DEXTR} -ENV DEXTR_MODEL_DIR=${HOME}/dextr -RUN if [ "$WITH_DEXTR" = "yes" ]; then \ - mkdir ${DEXTR_MODEL_DIR} -p && \ - curl https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -o ${DEXTR_MODEL_DIR}/dextr.zip && \ - 7z e ${DEXTR_MODEL_DIR}/dextr.zip -o${DEXTR_MODEL_DIR} && rm ${DEXTR_MODEL_DIR}/dextr.zip; \ +ARG CLAM_AV +ENV CLAM_AV=${CLAM_AV} +RUN if [ "$CLAM_AV" = "yes" ]; then \ + apt-get update && \ + apt-get --no-install-recommends install -yq \ + clamav \ + libclamunrar9 && \ + sed -i 's/ReceiveTimeout 30/ReceiveTimeout 300/g' /etc/clamav/freshclam.conf && \ + freshclam && \ + chown -R ${USER}:${USER} /var/lib/clamav && \ + rm -rf /var/lib/apt/lists/*; \ fi 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 -# Binary option is necessary to correctly apply the patch on Windows platform. -# https://unix.stackexchange.com/questions/239364/how-to-fix-hunk-1-failed-at-1-different-line-endings-message -RUN patch --binary -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch RUN chown -R ${USER}:${USER} . # RUN all commands below as 'django' user diff --git a/Dockerfile.ci b/Dockerfile.ci index 809c1d78..422259de 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,11 +1,11 @@ -FROM cvat +FROM cvat/server ENV DJANGO_CONFIGURATION=testing USER root RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \ - curl https://deb.nodesource.com/setup_9.x | bash - && \ + curl https://deb.nodesource.com/setup_12.x | bash - && \ apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -yq \ apt-utils \ @@ -23,19 +23,4 @@ RUN gem install coveralls-lcov COPY .coveragerc . -# RUN all commands below as 'django' user -USER ${USER} - -RUN mkdir -p tests && cd tests && npm install \ - eslint \ - eslint-detailed-reporter \ - karma \ - karma-chrome-launcher \ - karma-coveralls \ - karma-coverage \ - karma-junit-reporter \ - karma-qunit \ - qunit; \ - echo "export PATH=~/tests/node_modules/.bin:${PATH}" >> ~/.bashrc; - ENTRYPOINT [] diff --git a/Dockerfile.ui b/Dockerfile.ui index 48fd3c73..6af7763c 100644 --- a/Dockerfile.ui +++ b/Dockerfile.ui @@ -21,6 +21,8 @@ COPY cvat-canvas/package*.json /tmp/cvat-canvas/ COPY cvat-ui/package*.json /tmp/cvat-ui/ COPY cvat-data/package*.json /tmp/cvat-data/ +RUN npm config set loglevel info + # Install cvat-data dependencies WORKDIR /tmp/cvat-data/ RUN npm install diff --git a/README.md b/README.md index c1c9ac2e..275247ac 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,13 @@ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/840351da141e4eaeac6476fd19ec0a33)](https://app.codacy.com/app/cvat/cvat?utm_source=github.com&utm_medium=referral&utm_content=opencv/cvat&utm_campaign=Badge_Grade_Dashboard) [![Gitter chat](https://badges.gitter.im/opencv-cvat/gitter.png)](https://gitter.im/opencv-cvat) [![Coverage Status](https://coveralls.io/repos/github/opencv/cvat/badge.svg?branch=)](https://coveralls.io/github/opencv/cvat?branch=develop) -[![codebeat badge](https://codebeat.co/badges/53cd0d16-fddc-46f8-903c-f43ed9abb6dd)](https://codebeat.co/projects/github-com-opencv-cvat-develop) [![DOI](https://zenodo.org/badge/139156354.svg)](https://zenodo.org/badge/latestdoi/139156354) CVAT is free, online, interactive video and image annotation tool for computer vision. It is being used by our team to annotate million of objects with different properties. Many UI -and UX decisions are based on feedbacks from professional data annotation team. -Try it online [cvat.org](https://cvat.org). +and UX decisions are based on feedbacks from professional data +annotation team. Try it online [cvat.org](https://cvat.org). ![CVAT screenshot](cvat/apps/documentation/static/documentation/images/cvat.jpg) @@ -24,17 +23,20 @@ Try it online [cvat.org](https://cvat.org). - [Command line interface](utils/cli/) - [XML annotation format](cvat/apps/documentation/xml_format.md) - [AWS Deployment Guide](cvat/apps/documentation/AWS-Deployment-Guide.md) +- [Frequently asked questions](cvat/apps/documentation/faq.md) - [Questions](#questions) ## Screencasts -- [Introduction](https://youtu.be/L9_IvUIHGwM) -- [Annotation mode](https://youtu.be/6h7HxGL6Ct4) -- [Interpolation mode](https://youtu.be/U3MYDhESHo4) -- [Attribute mode](https://youtu.be/UPNfWl8Egd8) -- [Segmentation mode](https://youtu.be/Fh8oKuSUIPs) -- [Tutorial for polygons](https://www.youtube.com/watch?v=XTwfXDh4clI) -- [Semi-automatic segmentation](https://www.youtube.com/watch?v=vnqXZ-Z-VTQ) +- [Introduction](https://youtu.be/JERohTFp-NI) +- [Annotation mode](https://youtu.be/vH_639N67HI) +- [Interpolation of bounding boxes](https://youtu.be/Hc3oudNuDsY) +- [Interpolation of polygons](https://youtu.be/K4nis9lk92s) +- [Tag_annotation_video](https://youtu.be/62bI4mF-Xfk) +- [Attribute mode](https://youtu.be/iIkJsOkDzVA) +- [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 @@ -56,10 +58,18 @@ via its command line tool and Python library. | [MOT](https://motchallenge.net/) | X | X | | [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X | -## Links -- [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat) -- [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation) -- [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/) +## Deep learning models for automatic labeling + +| Name | Type | Framework | +| ------------------------------------------------------------------------------------------------------- | ---------- | ---------- | +| [Deep Extreme Cut](/serverless/openvino/dextr/nuclio) | interactor | OpenVINO | +| [Faster RCNN](/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio) | detector | TensorFlow | +| [Mask RCNN](/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio) | detector | OpenVINO | +| [YOLO v3](/serverless/openvino/omz/public/yolo-v3-tf/nuclio) | detector | OpenVINO | +| [Text detection v4](/serverless/openvino/omz/intel/text-detection-0004/nuclio) | detector | OpenVINO | +| [Semantic segmentation for ADAS](/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio) | detector | OpenVINO | +| [Mask RCNN](/serverless/tensorflow/matterport/mask_rcnn/nuclio) | detector | TensorFlow | +| [Object reidentification](/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio) | reid | OpenVINO | ## Online demo: [cvat.org](https://cvat.org) @@ -69,7 +79,6 @@ are visible to users. Disabled features: - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) -- [Support for NVIDIA GPUs](/components/cuda/README.md) Limitations: - No more than 10 tasks per user @@ -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: * [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow* * [Forum on Intel Developer Zone](https://software.intel.com/en-us/forums/computer-vision) + +## Links +- [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat) +- [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation) +- [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/) diff --git a/components/analytics/docker-compose.analytics.yml b/components/analytics/docker-compose.analytics.yml index fcb88272..5edcd505 100644 --- a/components/analytics/docker-compose.analytics.yml +++ b/components/analytics/docker-compose.analytics.yml @@ -31,7 +31,7 @@ services: cvat_kibana_setup: container_name: cvat_kibana_setup - image: cvat + image: cvat/server volumes: ['./components/analytics/kibana:/home/django/kibana:ro'] depends_on: ['cvat'] working_dir: '/home/django' @@ -63,7 +63,7 @@ services: DJANGO_LOG_SERVER_PORT: 5000 DJANGO_LOG_VIEWER_HOST: kibana DJANGO_LOG_VIEWER_PORT: 5601 - no_proxy: kibana,logstash,${no_proxy} + no_proxy: kibana,logstash,nuclio,${no_proxy} volumes: cvat_events: diff --git a/components/analytics/kibana/export.json b/components/analytics/kibana/export.json index 477b1f4d..d4896f5b 100644 --- a/components/analytics/kibana/export.json +++ b/components/analytics/kibana/export.json @@ -99,10 +99,10 @@ "_id": "ec510550-c238-11e8-8e1b-758ef07f6de8", "_type": "index-pattern", "_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*", "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": { "savedObjectVersion": 2 @@ -147,7 +147,7 @@ "_type": "visualization", "_source": { "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}}}}", "description": "", "version": 1, @@ -164,7 +164,7 @@ "_type": "visualization", "_source": { "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\"}}}}", "description": "", "version": 1, diff --git a/components/analytics/logstash/logstash.conf b/components/analytics/logstash/logstash.conf index 34f13a93..cbf893f6 100644 --- a/components/analytics/logstash/logstash.conf +++ b/components/analytics/logstash/logstash.conf @@ -65,6 +65,7 @@ filter { mutate { replace => { "type" => "client" } + rename => ["working time", "working_time"] copy => { "job_id" => "task" "username" => "userid" diff --git a/components/auto_segmentation/README.md b/components/auto_segmentation/README.md deleted file mode 100644 index b456857e..00000000 --- a/components/auto_segmentation/README.md +++ /dev/null @@ -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 -``` diff --git a/components/auto_segmentation/docker-compose.auto_segmentation.yml b/components/auto_segmentation/docker-compose.auto_segmentation.yml deleted file mode 100644 index 1d9763cd..00000000 --- a/components/auto_segmentation/docker-compose.auto_segmentation.yml +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -version: "2.3" - -services: - cvat: - build: - context: . - args: - AUTO_SEGMENTATION: "yes" diff --git a/components/auto_segmentation/install.sh b/components/auto_segmentation/install.sh deleted file mode 100755 index d6881a68..00000000 --- a/components/auto_segmentation/install.sh +++ /dev/null @@ -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 diff --git a/components/cuda/README.md b/components/cuda/README.md deleted file mode 100644 index a6ecbfef..00000000 --- a/components/cuda/README.md +++ /dev/null @@ -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 -``` diff --git a/components/cuda/docker-compose.cuda.yml b/components/cuda/docker-compose.cuda.yml deleted file mode 100644 index 41d325f3..00000000 --- a/components/cuda/docker-compose.cuda.yml +++ /dev/null @@ -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" diff --git a/components/cuda/install.sh b/components/cuda/install.sh deleted file mode 100755 index 58f99acf..00000000 --- a/components/cuda/install.sh +++ /dev/null @@ -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 - diff --git a/components/openvino/README.md b/components/openvino/README.md deleted file mode 100644 index 3b3a123b..00000000 --- a/components/openvino/README.md +++ /dev/null @@ -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. diff --git a/components/openvino/docker-compose.openvino.yml b/components/openvino/docker-compose.openvino.yml deleted file mode 100644 index 3805f1b8..00000000 --- a/components/openvino/docker-compose.openvino.yml +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -version: "2.3" - -services: - cvat: - build: - context: . - args: - OPENVINO_TOOLKIT: "yes" diff --git a/components/openvino/eula.cfg b/components/openvino/eula.cfg deleted file mode 100644 index 7c34e8fe..00000000 --- a/components/openvino/eula.cfg +++ /dev/null @@ -1,3 +0,0 @@ -# Accept actual EULA from openvino installation archive. Valid values are: {accept, decline} -ACCEPT_EULA=accept - diff --git a/components/openvino/install.sh b/components/openvino/install.sh deleted file mode 100755 index 159ff32d..00000000 --- a/components/openvino/install.sh +++ /dev/null @@ -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 diff --git a/components/tf_annotation/README.md b/components/tf_annotation/README.md deleted file mode 100644 index 5a9a2c10..00000000 --- a/components/tf_annotation/README.md +++ /dev/null @@ -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 -``` diff --git a/components/tf_annotation/docker-compose.tf_annotation.yml b/components/tf_annotation/docker-compose.tf_annotation.yml deleted file mode 100644 index 89fd7844..00000000 --- a/components/tf_annotation/docker-compose.tf_annotation.yml +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -version: "2.3" - -services: - cvat: - build: - context: . - args: - TF_ANNOTATION: "yes" diff --git a/components/tf_annotation/install.sh b/components/tf_annotation/install.sh deleted file mode 100755 index 8dc03483..00000000 --- a/components/tf_annotation/install.sh +++ /dev/null @@ -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 diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index a538a9e7..808aa5ac 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -117,6 +117,7 @@ Canvas itself handles: mode(): Mode; cancel(): 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 | |--------------|------|-------|-------|------|-------|------|------|--------|-------------|-------------| -| html() | + | + | + | + | + | + | + | + | + | + | -| setup() | + | + | + | + | + | +/- | +/- | +/- | + | + | +| setup() | + | + | + | +/- | + | +/- | +/- | +/- | + | + | | activate() | + | - | - | - | - | - | - | - | - | - | | rotate() | + | + | + | + | + | + | + | + | + | + | | focus() | + | + | + | + | + | + | + | + | + | + | @@ -208,3 +208,6 @@ Standard JS events are used. | setZLayer() | + | + | + | + | + | + | + | + | + | + | 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. diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 50645fbd..b50b6223 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "1.1.1", + "version": "2.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3223,9 +3223,9 @@ "dev": true }, "elliptic": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", - "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -5949,9 +5949,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash._reinterpolate": { @@ -10431,9 +10431,9 @@ } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, "which": { diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 73a7c98a..8792ce08 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "1.1.1", + "version": "2.0.2", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index b7d6c56d..090ec163 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -42,7 +42,7 @@ polyline.cvat_shape_drawing_opacity { fill: white; cursor: default; 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; pointer-events: none; } @@ -54,47 +54,55 @@ polyline.cvat_shape_drawing_opacity { .cvat_canvas_shape_grouping { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; + fill: darkmagenta; } polyline.cvat_canvas_shape_grouping { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; + stroke: darkmagenta; } .cvat_canvas_shape_merging { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; + fill: blue; } polyline.cvat_canvas_shape_merging { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; + stroke: blue; } polyline.cvat_canvas_shape_splitting { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; + stroke: dodgerblue; } .cvat_canvas_shape_splitting { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; + fill: dodgerblue; } .cvat_canvas_shape_drawing { @extend .cvat_shape_drawing_opacity; + fill: white; stroke: black; } .cvat_canvas_zoom_selection { @extend .cvat_shape_action_dasharray; + stroke: #096dd9; fill-opacity: 0; } @@ -103,7 +111,8 @@ polyline.cvat_canvas_shape_splitting { 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; } @@ -130,20 +139,24 @@ polyline.cvat_canvas_shape_splitting { 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; } -.svg_select_points_lt:hover, .svg_select_points_rb:hover { +.svg_select_points_lt:hover, +.svg_select_points_rb:hover { 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 { 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; } @@ -151,12 +164,31 @@ polyline.cvat_canvas_shape_splitting { 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 { width: calc(100% - 10px); height: calc(100% - 10px); margin: 5px; border-radius: 5px; - background-color: white; + background-color: inherit; overflow: hidden; position: relative; } @@ -183,7 +215,6 @@ polyline.cvat_canvas_shape_splitting { pointer-events: none; width: 100%; height: 100%; - pointer-events: none; } #cvat_canvas_background { @@ -192,7 +223,7 @@ polyline.cvat_canvas_shape_splitting { background-repeat: no-repeat; width: 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 { @@ -202,7 +233,7 @@ polyline.cvat_canvas_shape_splitting { background: black; width: 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 { @@ -211,7 +242,6 @@ polyline.cvat_canvas_shape_splitting { pointer-events: none; width: 100%; height: 100%; - pointer-events: none; } #cvat_canvas_grid_pattern { @@ -229,7 +259,18 @@ polyline.cvat_canvas_shape_splitting { } @keyframes loadingAnimation { - 0% {stroke-dashoffset: 1; stroke: #09c;} - 50% {stroke-dashoffset: 100; stroke: #f44;} - 100% {stroke-dashoffset: 300; stroke: #09c;} + 0% { + stroke-dashoffset: 1; + stroke: #09c; + } + + 50% { + stroke-dashoffset: 100; + stroke: #f44; + } + + 100% { + stroke-dashoffset: 300; + stroke: #09c; + } } diff --git a/cvat-canvas/src/typescript/autoborderHandler.ts b/cvat-canvas/src/typescript/autoborderHandler.ts index 8f042948..198a485e 100644 --- a/cvat-canvas/src/typescript/autoborderHandler.ts +++ b/cvat-canvas/src/typescript/autoborderHandler.ts @@ -13,14 +13,14 @@ interface TransformedShape { } 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; updateObjects(): void; } export class AutoborderHandlerImpl implements AutoborderHandler { private currentShape: SVG.Shape | null; - private ignoreCurrent: boolean; + private currentID?: number; private frameContent: SVGSVGElement; private enabled: boolean; private scale: number; @@ -34,7 +34,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler { public constructor(frameContent: SVGSVGElement) { this.frameContent = frameContent; - this.ignoreCurrent = false; + this.currentID = undefined; this.currentShape = null; this.enabled = false; this.scale = 1; @@ -231,7 +231,8 @@ export class AutoborderHandlerImpl implements AutoborderHandler { this.removeMarkers(); 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 color = shape.getAttribute('fill'); const clientID = shape.getAttribute('clientID'); @@ -277,12 +278,12 @@ export class AutoborderHandlerImpl implements AutoborderHandler { public autoborder( enabled: boolean, currentShape?: SVG.Shape, - ignoreCurrent: boolean = false, + currentID?: number, ): void { if (enabled && !this.enabled && currentShape) { this.enabled = true; this.currentShape = currentShape; - this.ignoreCurrent = ignoreCurrent; + this.currentID = currentID; this.updateObjects(); } else { this.release(); diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 61f3b0c8..35699766 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -36,8 +36,7 @@ const CanvasVersion = pjson.version; interface Canvas { html(): HTMLDivElement; - setZLayer(zLayer: number | null): void; - setup(frameData: any, objectStates: any[]): void; + setup(frameData: any, objectStates: any[], zLayer?: number): void; activate(clientID: number | null, attributeID?: number): void; rotate(rotationAngle: number): void; focus(clientID: number, padding?: number): void; @@ -58,6 +57,7 @@ interface Canvas { mode(): Mode; cancel(): void; configure(configuration: Configuration): void; + isAbleToChangeFrame(): boolean; } class CanvasImpl implements Canvas { @@ -75,12 +75,8 @@ class CanvasImpl implements Canvas { return this.view.html(); } - public setZLayer(zLayer: number | null): void { - this.model.setZLayer(zLayer); - } - - public setup(frameData: any, objectStates: any[]): void { - this.model.setup(frameData, objectStates); + public setup(frameData: any, objectStates: any[], zLayer = 0): void { + this.model.setup(frameData, objectStates, zLayer); } public fitCanvas(): void { @@ -153,6 +149,10 @@ class CanvasImpl implements Canvas { public configure(configuration: Configuration): void { this.model.configure(configuration); } + + public isAbleToChangeFrame(): boolean { + return this.model.isAbleToChangeFrame(); + } } export { diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 55873e4a..ec860e77 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -66,6 +66,7 @@ export interface DrawData { numberOfPoints?: number; initialState?: any; crosshair?: boolean; + redraw?: number; } export interface EditData { @@ -97,7 +98,6 @@ export enum UpdateReasons { IMAGE_FITTED = 'image_fitted', IMAGE_MOVED = 'image_moved', GRID_UPDATED = 'grid_updated', - SET_Z_LAYER = 'set_z_layer', OBJECTS_UPDATED = 'objects_updated', SHAPE_ACTIVATED = 'shape_activated', @@ -147,11 +147,10 @@ export interface CanvasModel { geometry: Geometry; mode: Mode; - setZLayer(zLayer: number | null): void; zoom(x: number, y: number, direction: 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; rotate(rotationAngle: number): void; focus(clientID: number, padding: number): void; @@ -169,6 +168,7 @@ export interface CanvasModel { dragCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void; + isAbleToChangeFrame(): boolean; configure(configuration: Configuration): 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 { const oldScale: number = this.data.scale; 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); } - public setup(frameData: any, objectStates: any[]): void { + public setup(frameData: any, objectStates: any[], zLayer: number): void { if (this.data.imageID !== frameData.number) { if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(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) { + this.data.zLayer = zLayer; this.data.objects = objectStates; this.notify(UpdateReasons.OBJECTS_UPDATED); return; @@ -367,6 +363,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.image = data; this.notify(UpdateReasons.IMAGE_CHANGED); + this.data.zLayer = zLayer; this.data.objects = objectStates; this.notify(UpdateReasons.OBJECTS_UPDATED); }).catch((exception: any): void => { @@ -382,10 +379,17 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } if (this.data.mode !== Mode.IDLE && clientID !== null) { - // Exception or just return? 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 = { clientID, attributeID, @@ -465,10 +469,24 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } } - this.data.drawData = { ...drawData }; - if (this.data.drawData.initialState) { - this.data.drawData.shapeType = this.data.drawData.initialState.shapeType; + if (typeof (drawData.redraw) === 'number') { + const clientID = drawData.redraw; + 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); } @@ -548,6 +566,13 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { 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 { this.notify(UpdateReasons.CANCEL); } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 002b4f09..4eb95a8f 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -21,8 +21,11 @@ import consts from './consts'; import { translateToSVG, translateFromSVG, - pointsToArray, + pointsToNumberArray, + parsePoints, displayShapeSize, + scalarProduct, + vectorLength, ShapeSizeElement, DrawnState, } from './shared'; @@ -71,6 +74,9 @@ export class CanvasViewImpl implements CanvasView, Listener { private autoborderHandler: AutoborderHandler; private activeElement: ActiveElement; private configuration: Configuration; + private serviceFlags: { + drawHidden: Record; + }; private set mode(value: Mode) { this.controller.mode = value; @@ -80,8 +86,75 @@ export class CanvasViewImpl implements CanvasView, Listener { 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 { + 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) { + 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 event: CustomEvent = new CustomEvent('canvas.drawn', { 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 window.document.getElementsByClassName('cvat_canvas_selected_point')) { 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 { const self = this; const { offset } = this.controller.geometry; const translate = (points: number[]): number[] => points .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 .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 => ( _state.clientID === self.activeElement.clientID )); - if (state.shapeType === 'rectangle') { - e.preventDefault(); - return; + + if (['polygon', 'polyline', 'points'].includes(state.shapeType)) { + 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 (e.shiftKey) { - const points = translate(pointsToArray((e.target as any) + const points = translate(pointsToNumberArray((e.target as any) .parentElement.parentElement.instance.attr('points'))); self.onEditDone( state, 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 { @@ -524,6 +688,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.on('dblclick', dblClickHandler); + circle.on('mousedown', mousedownHandler); circle.on('contextmenu', contextMenuHandler); circle.addClass('cvat_canvas_selected_point'); }); @@ -534,6 +699,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.off('dblclick', dblClickHandler); + circle.off('mousedown', mousedownHandler); circle.off('contextmenu', contextMenuHandler); circle.removeClass('cvat_canvas_selected_point'); }); @@ -565,6 +731,9 @@ export class CanvasViewImpl implements CanvasView, Listener { }; this.configuration = model.configuration; this.mode = Mode.IDLE; + this.serviceFlags = { + drawHidden: {}, + }; // Create HTML elements this.loadingAnimation = window.document @@ -742,12 +911,32 @@ export class CanvasViewImpl implements CanvasView, Listener { if (reason === UpdateReasons.CONFIG_UPDATED) { const { activeElement } = this; 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.activate(activeElement); this.editHandler.configurate(this.configuration); this.drawHandler.configurate(this.configuration); - // todo: setup text, add if doesn't exist and enabled // remove if exist and not enabled // this.setupObjects([]); // this.setupObjects(model.objects); @@ -802,7 +991,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } } else if (reason === UpdateReasons.IMAGE_MOVED) { 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) { this.groupHandler.resetSelectedObjects(); } @@ -864,6 +1053,9 @@ export class CanvasViewImpl implements CanvasView, Listener { if (data.enabled && this.mode === Mode.IDLE) { this.canvas.style.cursor = 'crosshair'; this.mode = Mode.DRAW; + if (typeof (data.redraw) === 'number') { + this.setupServiceHidden(data.redraw, true); + } this.drawHandler.draw(data, this.geometry); } else { this.canvas.style.cursor = ''; @@ -936,7 +1128,6 @@ export class CanvasViewImpl implements CanvasView, Listener { if (model.imageBitmap && [UpdateReasons.IMAGE_CHANGED, UpdateReasons.OBJECTS_UPDATED, - UpdateReasons.SET_Z_LAYER, ].includes(reason) ) { this.redrawBitmap(); @@ -1036,6 +1227,7 @@ export class CanvasViewImpl implements CanvasView, Listener { pinned: state.pinned, updated: state.updated, frame: state.frame, + label: state.label, }; } @@ -1045,18 +1237,19 @@ export class CanvasViewImpl implements CanvasView, Listener { const drawnState = this.drawnStates[clientID]; const shape = this.svgShapes[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 (isInvisible) { (state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape) - .style('display', 'none'); + .addClass('cvat_canvas_hidden'); if (text) { text.addClass('cvat_canvas_hidden'); } } else { (state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape) - .style('display', ''); + .removeClass('cvat_canvas_hidden'); if (text) { text.removeClass('cvat_canvas_hidden'); this.updateTextPosition( @@ -1147,59 +1340,57 @@ export class CanvasViewImpl implements CanvasView, Listener { const { displayAllText } = this.configuration; for (const state of states) { - if (state.objectType === 'tag') { - this.addTag(state); + const points: number[] = (state.points as number[]); + 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 { - const points: number[] = (state.points as number[]); - const translatedPoints: number[] = translate(points); + const stringified = translatedPoints.reduce( + (acc: string, val: number, idx: number): string => { + if (idx % 2) { + return `${acc}${val} `; + } - // TODO: Use enums after typification cvat-core - if (state.shapeType === 'rectangle') { + return `${acc}${val},`; + }, '', + ); + + if (state.shapeType === 'polygon') { 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 { - const stringified = translatedPoints.reduce( - (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); - } + continue; } + } - this.svgShapes[state.clientID].on('click.canvas', (): void => { - this.canvas.dispatchEvent(new CustomEvent('canvas.clicked', { - bubbles: false, - cancelable: true, - detail: { - state, - }, - })); - }); + this.svgShapes[state.clientID].on('click.canvas', (): void => { + this.canvas.dispatchEvent(new CustomEvent('canvas.clicked', { + bubbles: false, + cancelable: true, + detail: { + state, + }, + })); + }); - if (displayAllText) { - this.svgTexts[state.clientID] = this.addText(state); - this.updateTextPosition( - this.svgTexts[state.clientID], - this.svgShapes[state.clientID], - ); - } + if (displayAllText) { + this.svgTexts[state.clientID] = this.addText(state); + this.updateTextPosition( + this.svgTexts[state.clientID], + this.svgShapes[state.clientID], + ); } this.saveState(state); @@ -1327,16 +1518,6 @@ export class CanvasViewImpl implements CanvasView, Listener { 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) { return; } @@ -1354,29 +1535,39 @@ export class CanvasViewImpl implements CanvasView, Listener { (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) { shape.addClass('cvat_canvas_shape_draggable'); (shape as any).draggable().on('dragstart', (): void => { this.mode = Mode.DRAG; - if (text) { - text.addClass('cvat_canvas_hidden'); - } + hideText(); }).on('dragend', (e: CustomEvent): void => { - if (text) { - text.removeClass('cvat_canvas_hidden'); - this.updateTextPosition( - text, - shape, - ); - } - + showText(); this.mode = Mode.IDLE; const p1 = e.detail.handler.startPoints.point; const p2 = e.detail.p; const delta = 1; const { offset } = this.controller.geometry; 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('x') + shape.attr('width')},` + `${shape.attr('y') + shape.attr('height')}`, @@ -1399,17 +1590,32 @@ export class CanvasViewImpl implements CanvasView, Listener { 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 resized = false; - (shape as any).resize().on('resizestart', (): void => { + (shape as any).resize({ + snapToGrid: 0.1, + }).on('resizestart', (): void => { this.mode = Mode.RESIZE; + resized = false; + hideDirection(); + hideText(); if (state.shapeType === 'rectangle') { shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); } - resized = false; - if (text) { - text.addClass('cvat_canvas_hidden'); - } }).on('resizing', (): void => { resized = true; if (shapeSizeElement) { @@ -1420,20 +1626,15 @@ export class CanvasViewImpl implements CanvasView, Listener { shapeSizeElement.rm(); } - if (text) { - text.removeClass('cvat_canvas_hidden'); - this.updateTextPosition( - text, - shape, - ); - } + showDirection(); + showText(); this.mode = Mode.IDLE; if (resized) { const { offset } = this.controller.geometry; - const points = pointsToArray( + const points = pointsToNumberArray( shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + `${shape.attr('x') + shape.attr('width')},` + `${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', { bubbles: false, cancelable: true, @@ -1530,14 +1732,19 @@ export class CanvasViewImpl implements CanvasView, Listener { private addText(state: any): SVG.Text { 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 => { acc[val.id] = val.name; return acc; }, {}); 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)) { const value = attributes[attrID] === undefinedAttrValue ? '' : attributes[attrID]; @@ -1568,8 +1775,8 @@ export class CanvasViewImpl implements CanvasView, Listener { rect.addClass('cvat_canvas_shape_occluded'); } - if (state.hidden || state.outside) { - rect.style('display', 'none'); + if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) { + rect.addClass('cvat_canvas_hidden'); } return rect; @@ -1591,8 +1798,8 @@ export class CanvasViewImpl implements CanvasView, Listener { polygon.addClass('cvat_canvas_shape_occluded'); } - if (state.hidden || state.outside) { - polygon.style('display', 'none'); + if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) { + polygon.addClass('cvat_canvas_hidden'); } return polygon; @@ -1614,8 +1821,8 @@ export class CanvasViewImpl implements CanvasView, Listener { polyline.addClass('cvat_canvas_shape_occluded'); } - if (state.hidden || state.outside) { - polyline.style('display', 'none'); + if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) { + polyline.addClass('cvat_canvas_hidden'); } return polyline; @@ -1638,8 +1845,8 @@ export class CanvasViewImpl implements CanvasView, Listener { cube.addClass('cvat_canvas_shape_occluded'); } - if (state.hidden || state.outside) { - cube.style('display', 'none'); + if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) { + cube.addClass('cvat_canvas_hidden'); } return cube; @@ -1682,8 +1889,8 @@ export class CanvasViewImpl implements CanvasView, Listener { const group = this.setupPoints(shape, state); - if (state.hidden || state.outside) { - group.style('display', 'none'); + if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) { + group.addClass('cvat_canvas_hidden'); } shape.remove = (): SVG.PolyLine => { @@ -1694,9 +1901,4 @@ export class CanvasViewImpl implements CanvasView, Listener { return shape; } - - /* eslint-disable-next-line */ - private addTag(state: any): void { - console.log(state); - } } diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index aa92a9f2..b9110b39 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -14,6 +14,8 @@ const MIN_EDGE_LENGTH = 3; const CUBOID_ACTIVE_EDGE_STROKE_WIDTH = 2.5; const CUBOID_UNACTIVE_EDGE_STROKE_WIDTH = 1.75; 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 { BASE_STROKE_WIDTH, @@ -28,4 +30,5 @@ export default { CUBOID_ACTIVE_EDGE_STROKE_WIDTH, CUBOID_UNACTIVE_EDGE_STROKE_WIDTH, UNDEFINED_ATTRIBUTE_VALUE, + ARROW_PATH, }; diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 5a52badd..404157bd 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -11,8 +11,8 @@ import { translateToSVG, displayShapeSize, ShapeSizeElement, - pointsToString, - pointsToArray, + stringifyPoints, + pointsToNumberArray, BBox, Box, } from './shared'; @@ -264,12 +264,13 @@ export class DrawHandlerImpl implements DrawHandler { this.drawInstance.on('drawstop', (e: Event): void => { const bbox = (e.target as SVGRectElement).getBBox(); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); - const { shapeType } = this.drawData; + const { shapeType, redraw: clientID } = this.drawData; this.release(); if (this.canceled) return; if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { this.onDrawDone({ + clientID, shapeType, points: [xtl, ytl, xbr, ybr], }, Date.now() - this.startTimestamp); @@ -298,12 +299,13 @@ export class DrawHandlerImpl implements DrawHandler { if (numberOfPoints === 4) { const bbox = (e.target as SVGPolylineElement).getBBox(); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); - const { shapeType } = this.drawData; + const { shapeType, redraw: clientID } = this.drawData; this.cancel(); if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { this.onDrawDone({ shapeType, + clientID, points: [xtl, ytl, xbr, ybr], }, Date.now() - this.startTimestamp); } @@ -356,6 +358,7 @@ export class DrawHandlerImpl implements DrawHandler { if (lastDrawnPoint.x === null || lastDrawnPoint.y === null) { this.drawInstance.draw('point', e); } else { + this.drawInstance.draw('update', e); const deltaTreshold = 15; const delta = Math.sqrt( ((e.clientX - lastDrawnPoint.x) ** 2) @@ -379,8 +382,8 @@ export class DrawHandlerImpl implements DrawHandler { }); this.drawInstance.on('drawdone', (e: CustomEvent): void => { - const targetPoints = pointsToArray((e.target as SVGElement).getAttribute('points')); - const { shapeType } = this.drawData; + const targetPoints = pointsToNumberArray((e.target as SVGElement).getAttribute('points')); + const { shapeType, redraw: clientID } = this.drawData; const { points, box } = shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints) : this.getFinalPolyshapeCoordinates(targetPoints); this.release(); @@ -390,6 +393,7 @@ export class DrawHandlerImpl implements DrawHandler { && ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD) && points.length >= 3 * 2) { this.onDrawDone({ + clientID, shapeType, points, }, Date.now() - this.startTimestamp); @@ -398,12 +402,14 @@ export class DrawHandlerImpl implements DrawHandler { || (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD) && points.length >= 2 * 2) { this.onDrawDone({ + clientID, shapeType, points, }, Date.now() - this.startTimestamp); } else if (shapeType === 'points' && (e.target as any).getAttribute('points') !== '0,0') { this.onDrawDone({ + clientID, shapeType, points, }, Date.now() - this.startTimestamp); @@ -411,6 +417,7 @@ export class DrawHandlerImpl implements DrawHandler { } else if (shapeType === 'cuboid' && points.length === 4 * 2) { this.onDrawDone({ + clientID, shapeType, points: cuboidFrom4Points(points), }, Date.now() - this.startTimestamp); @@ -426,7 +433,7 @@ export class DrawHandlerImpl implements DrawHandler { this.drawPolyshape(); 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(); 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 ((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({ shapeType, points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]), @@ -673,7 +680,7 @@ export class DrawHandlerImpl implements DrawHandler { } else { const points = this.drawData.initialState.points .map((coord: number): number => coord + offset); - const stringifiedPoints = pointsToString(points); + const stringifiedPoints = stringifyPoints(points); if (this.drawData.shapeType === 'polygon') { this.pastePolygon(stringifiedPoints); @@ -760,7 +767,11 @@ export class DrawHandlerImpl implements DrawHandler { this.autobordersEnabled = configuration.autoborders; if (this.drawInstance) { if (this.autobordersEnabled) { - this.autoborderHandler.autoborder(true, this.drawInstance, false); + this.autoborderHandler.autoborder( + true, + this.drawInstance, + this.drawData.redraw, + ); } else { this.autoborderHandler.autoborder(false); } diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index 74446096..8cea079d 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -6,7 +6,7 @@ import * as SVG from 'svg.js'; import 'svg.select.js'; import consts from './consts'; -import { translateFromSVG, pointsToArray } from './shared'; +import { translateFromSVG, pointsToNumberArray } from './shared'; import { EditData, Geometry, Configuration } from './canvasModel'; import { AutoborderHandler } from './autoborderHandler'; @@ -28,6 +28,38 @@ export class EditHandlerImpl implements EditHandler { private clones: SVG.Polygon[]; 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 { // get started coordinates const [clientX, clientY] = translateFromSVG( @@ -72,9 +104,18 @@ export class EditHandlerImpl implements EditHandler { }); 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({ 'pointer-events': 'none', 'fill-opacity': 0, + 'stroke': strokeColor, }).attr({ 'data-origin-client-id': this.editData.state.clientID, }).on('drawstart drawpoint', (e: CustomEvent): void => { @@ -90,7 +131,7 @@ export class EditHandlerImpl implements EditHandler { this.setupEditEvents(); 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 { const { offset } = this.geometry; - const points = pointsToArray(shape.attr('points')) + const points = pointsToNumberArray(shape.attr('points')) .map((coord: number): number => coord - offset); const { state } = this.editData; @@ -140,24 +181,87 @@ export class EditHandlerImpl implements EditHandler { const [start, stop] = [this.editData.pointID, stopPointID] .sort((a, b): number => +a - +b); - if (this.editData.state.shapeType === 'polygon') { - if (start !== this.editData.pointID) { - linePoints.reverse(); + if (this.editData.state.shapeType !== 'polygon') { + 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)); } - const firstPart = oldPoints.slice(0, start) - .concat(linePoints) - .concat(oldPoints.slice(stop + 1)); + points = pointsToNumberArray(points.join(' ')) + .map((coord: number): number => coord - offset); - linePoints.reverse(); - const secondPart = oldPoints.slice(start + 1, stop) - .concat(linePoints); + const { state } = this.editData; + this.edit({ + enabled: false, + }); + this.onEditDone(state, points); - if (firstPart.length < 3 || secondPart.length < 3) { - this.cancel(); - return; + 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]) { this.clones.push(this.canvas.polygon(points.join(' ')) .attr('fill', this.editedShape.attr('fill')) @@ -173,39 +277,9 @@ export class EditHandlerImpl implements EditHandler { 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(' ')) - .map((coord: number): number => coord - offset); - - const { state } = this.editData; - this.edit({ - enabled: false, - }); - this.onEditDone(state, points); + return; } private setupPoints(enabled: boolean): void { @@ -337,7 +411,11 @@ export class EditHandlerImpl implements EditHandler { this.autobordersEnabled = configuration.autoborders; if (this.editLine) { if (this.autobordersEnabled) { - this.autoborderHandler.autoborder(true, this.editLine, true); + this.autoborderHandler.autoborder( + true, + this.editLine, + this.editData.state.clientID, + ); } else { this.autoborderHandler.autoborder(false); } diff --git a/cvat-canvas/src/typescript/groupHandler.ts b/cvat-canvas/src/typescript/groupHandler.ts index 0c2475ef..f92cdab6 100644 --- a/cvat-canvas/src/typescript/groupHandler.ts +++ b/cvat-canvas/src/typescript/groupHandler.ts @@ -95,7 +95,9 @@ export class GroupHandlerImpl implements GroupHandler { this.selectionRect = null; 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) { // TODO: Doesn't work properly for groups const bbox = shape.bbox(); diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 0a06c72d..d1f9cb8c 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -29,6 +29,12 @@ interface Point { x: number; y: number; } + +interface Vector2D { + i: number; + j: number; +} + export interface DrawnState { clientID: number; outside?: boolean; @@ -42,6 +48,7 @@ export interface DrawnState { pinned?: boolean; updated: number; frame: number; + label: any; } // Translate point array from the canvas coordinate system @@ -76,21 +83,6 @@ export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] { 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( shapesContainer: SVG.Container, textContainer: SVG.Container, @@ -120,25 +112,59 @@ export function displayShapeSize( return shapeSize; } -export function convertToArray(points: Point[]): number[][] { - const arr: number[][] = []; - points.forEach((point: Point): void => { - arr.push([point.x, point.y]); - }); - return arr; +export function pointsToNumberArray(points: string | Point[]): number[] { + if (Array.isArray(points)) { + return points.reduce((acc: number[], point: Point): number[] => { + acc.push(point.x, point.y); + return acc; + }, []); + } + + return points.trim().split(/[,\s]+/g) + .map((coord: string): number => +coord); } -export function parsePoints(stringified: string): Point[] { - return stringified.trim().split(/\s/).map((point: string): Point => { +export function parsePoints(source: string | number[]): 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); 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(' '); } export function clamp(x: number, min: number, max: number): number { 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)); +} diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index f571accb..3d88c7da 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -17,7 +17,7 @@ import { Orientation, Edge, } from './cuboid'; -import { parsePoints, stringifyPoints, clamp } from './shared'; +import { parsePoints, clamp } from './shared'; // Update constructor const originalDraw = SVG.Element.prototype.draw; @@ -174,7 +174,8 @@ SVG.Element.prototype.resize = function constructor(...args: any): any { originalResize.call(this, ...args); handler = this.remember('_resizeHandler'); 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); } } diff --git a/cvat-canvas/webpack.config.js b/cvat-canvas/webpack.config.js index 2420c5e4..96723247 100644 --- a/cvat-canvas/webpack.config.js +++ b/cvat-canvas/webpack.config.js @@ -60,10 +60,12 @@ const webConfig = { target: 'web', mode: 'production', devtool: 'source-map', - entry: './src/typescript/canvas.ts', + entry: { + 'cvat-canvas': './src/typescript/canvas.ts', + }, output: { path: path.resolve(__dirname, 'dist'), - filename: 'cvat-canvas.js', + filename: '[name].[contenthash].js', library: 'canvas', libraryTarget: 'window', }, diff --git a/cvat-core/.gitignore b/cvat-core/.gitignore index 80b08369..b9d6abb3 100644 --- a/cvat-core/.gitignore +++ b/cvat-core/.gitignore @@ -1,6 +1,5 @@ docs node_modules reports -package-lock.json yarn.lock dist diff --git a/cvat-core/README.md b/cvat-core/README.md index 475f85bb..84ae6050 100644 --- a/cvat-core/README.md +++ b/cvat-core/README.md @@ -1,7 +1,7 @@ # Module CVAT-CORE ## 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. ## Versioning diff --git a/cvat-core/docs/Interpolation.pdf b/cvat-core/docs/Interpolation.pdf new file mode 100644 index 00000000..51e7d5ea Binary files /dev/null and b/cvat-core/docs/Interpolation.pdf differ diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json new file mode 100644 index 00000000..0bc2c787 --- /dev/null +++ b/cvat-core/package-lock.json @@ -0,0 +1,15508 @@ +{ + "name": "cvat-core", + "version": "3.5.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/cli": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.10.5.tgz", + "integrity": "sha512-j9H9qSf3kLdM0Ao3aGPbGZ73mEA9XazuupcS6cDGWuiyAcANoguhP0r2Lx32H5JGw4sSSoHG3x/mxVnHgvOoyA==", + "dev": true, + "requires": { + "chokidar": "^2.1.8", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.19", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + } + }, + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "semver": "^5.5.0" + } + }, + "@babel/core": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.1.tgz", + "integrity": "sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.0", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.1", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.0", + "@babel/types": "^7.11.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@babel/generator": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", + "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", + "requires": { + "@babel/types": "^7.11.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", + "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", + "dev": true, + "requires": { + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "@babel/helper-regex": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, + "@babel/helper-wrap-function": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.2.tgz", + "integrity": "sha512-Vuj/+7vLo6l1Vi7uuO+1ngCDNeVmNbTngcJFKCR/oEtz8tKz0CJxZEGmPt9KcIloZhOZ3Zit6xbpXT2MDlS9Vw==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", + "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", + "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/preset-env": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.0.tgz", + "integrity": "sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.11.0", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.11.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.11.0", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.11.0", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.11.0", + "browserslist": "^4.12.0", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", + "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", + "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.0", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.0", + "@babel/types": "^7.11.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", + "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "requires": { + "@jest/source-map": "^24.9.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/core": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", + "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/reporters": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.9.0", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-resolve-dependencies": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "jest-watcher": "^24.9.0", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "slash": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "@jest/environment": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", + "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "requires": { + "@jest/fake-timers": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/fake-timers": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", + "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "requires": { + "@jest/types": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/reporters": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", + "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.1", + "istanbul-reports": "^2.2.6", + "jest-haste-map": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.6.0", + "node-notifier": "^5.4.2", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/source-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@jest/test-result": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "requires": { + "@jest/console": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/test-sequencer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", + "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", + "requires": { + "@jest/test-result": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0" + } + }, + "@jest/transform": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", + "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.9.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.9.0", + "jest-regex-util": "^24.9.0", + "jest-util": "^24.9.0", + "micromatch": "^3.1.10", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/babel__core": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", + "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" + }, + "@types/yargs": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "abab": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz", + "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==" + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" + }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" + } + } + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" + }, + "airbnb": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/airbnb/-/airbnb-0.0.2.tgz", + "integrity": "sha512-eC+7zzGrcM///BKt04V23v+W3b9dWDUltOzo0j5lzjhvvMc4EiSxh55k2vlVnHTZ0igqA8/i/1j2j+m7UlZ54w==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true, + "optional": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" + }, + "axios": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "babel-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", + "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "requires": { + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.9.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", + "dev": true, + "requires": { + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", + "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", + "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.9.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bn.js": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-or-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz", + "integrity": "sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg==" + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + }, + "dependencies": { + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + } + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "caniuse-lite": { + "version": "1.0.30001111", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001111.tgz", + "integrity": "sha512-xnDje2wchd/8mlJu8sXvWxOGvMgv+uT3iZ3bkIAynKOzToCssWCmkz/ZIkQBs/2pUB4uwnJKVORWQ31UkbVjOg==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "catharsis": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", + "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true + } + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-js-compat": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "dev": true, + "requires": { + "browserslist": "^4.8.5", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "coveralls": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", + "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "requires": { + "cssom": "0.3.x" + } + }, + "cvat-data": { + "version": "file:../cvat-data", + "requires": { + "async-mutex": "^0.1.4", + "jszip": "3.1.5" + }, + "dependencies": { + "@babel/cli": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.6.4.tgz", + "integrity": "sha512-tqrDyvPryBM6xjIyKKUwr3s8CzmmYidwgdswd7Uc/Cv0ogZcuS1TYQTLx/eWKP3UbJ6JxZAiYlBZabXm/rtRsQ==", + "requires": { + "chokidar": "^2.1.8", + "commander": "^2.8.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.13", + "mkdirp": "^0.5.1", + "output-file-sync": "^2.0.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + } + }, + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", + "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.4", + "@babel/helpers": "^7.6.2", + "@babel/parser": "^7.6.4", + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.3", + "@babel/types": "^7.6.3", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@babel/generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", + "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", + "requires": { + "@babel/types": "^7.6.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-define-map": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", + "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "requires": { + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", + "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + }, + "@babel/helper-regex": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", + "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", + "requires": { + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", + "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", + "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", + "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", + "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", + "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz", + "integrity": "sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", + "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5", + "@babel/helper-split-export-declaration": "^7.4.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz", + "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz", + "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", + "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", + "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz", + "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==", + "requires": { + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", + "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz", + "integrity": "sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==", + "requires": { + "regexpu-core": "^4.6.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", + "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "requires": { + "@babel/helper-call-delegate": "^7.4.4", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "requires": { + "regenerator-transform": "^0.14.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz", + "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz", + "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/preset-env": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.3.tgz", + "integrity": "sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.6.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.5.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.6.3", + "@babel/plugin-transform-classes": "^7.5.5", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.6.0", + "@babel/plugin-transform-dotall-regex": "^7.6.2", + "@babel/plugin-transform-duplicate-keys": "^7.5.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.4", + "@babel/plugin-transform-function-name": "^7.4.4", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.6.0", + "@babel/plugin-transform-modules-systemjs": "^7.5.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.3", + "@babel/plugin-transform-new-target": "^7.4.4", + "@babel/plugin-transform-object-super": "^7.5.5", + "@babel/plugin-transform-parameters": "^7.4.4", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.5", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.6.2", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.4.4", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.6.2", + "@babel/types": "^7.6.3", + "browserslist": "^4.6.0", + "core-js-compat": "^3.1.1", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + } + }, + "@babel/template": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + } + }, + "@babel/traverse": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", + "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.3", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.3", + "@babel/types": "^7.6.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@babel/types": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", + "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "requires": { + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==" + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "requires": { + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==" + }, + "@webassemblyjs/helper-module-context": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/helper-wasm-section": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-opt": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/floating-point-hex-parser": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-code-frame": "1.8.5", + "@webassemblyjs/helper-fsm": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==" + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-mutex": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.4.tgz", + "integrity": "sha512-zVWTmAnxxHaeB2B1te84oecI8zTDJ/8G49aVBblRX6be0oq6pAybNcUSxwfgVOmOjSCvN4aYZAqwtyNI8e1YGw==" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "babel-loader": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", + "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "requires": { + "find-cache-dir": "^2.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "pify": "^4.0.1" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.1.tgz", + "integrity": "sha512-QtULFqKIAtiyNx7NhZ/p4rB8m3xDozVo/pi5VgTlADLF2tNigz/QH+v0m5qhn7XfHT7u+607NcCNOnC0HZAlMg==", + "requires": { + "caniuse-lite": "^1.0.30000999", + "electron-to-chromium": "^1.3.284", + "node-releases": "^1.1.36" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + }, + "dependencies": { + "bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "requires": { + "callsites": "^0.2.0" + }, + "dependencies": { + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + } + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "caniuse-lite": { + "version": "1.0.30001002", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001002.tgz", + "integrity": "sha512-pRuxPE8wdrWmVPKcDmJJiGBxr6lFJq4ivdSeo9FTmGj5Rb8NX3Mby2pARG57MXF15hYAhZ0nHV5XxT2ig4bz3g==" + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "chownr": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==" + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "requires": { + "date-now": "^0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "copy-webpack-plugin": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.5.tgz", + "integrity": "sha512-7N68eIoQTyudAuxkfPT7HzGoQ+TsmArN/I3HFwG+lVE3FNzqvZKIiaxtYh4o3BIznioxUvx9j26+Rtsc9htQUQ==", + "requires": { + "cacache": "^12.0.3", + "find-cache-dir": "^2.1.0", + "glob-parent": "^3.1.0", + "globby": "^7.1.1", + "is-glob": "^4.0.1", + "loader-utils": "^1.2.3", + "minimatch": "^3.0.4", + "normalize-path": "^3.0.0", + "p-limit": "^2.2.1", + "serialize-javascript": "^2.1.0", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + } + } + }, + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=" + }, + "core-js-compat": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.3.2.tgz", + "integrity": "sha512-gfiK4QnNXhnnHVOIZst2XHdFfdMTPxtR0EGs0TdILMlGIft+087oH6/Sw2xTTIjpWXC9vEwsJA8VG3XTGcmO5g==", + "requires": { + "browserslist": "^4.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "requires": { + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "electron-to-chromium": { + "version": "1.3.289", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.289.tgz", + "integrity": "sha512-39GEOWgTxtMDk/WjIQLg4W/l1s4FZdiMCqUBLjd92tAXsBPDFLwuwCba5OGhuTdVYm6E128TZIqSnMpeocUlCQ==" + }, + "elliptic": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", + "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.51", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", + "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=" + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", + "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.51" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.5.1.tgz", + "integrity": "sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==", + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz", + "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==", + "requires": { + "confusing-browser-globals": "^1.0.7", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-module-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", + "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-plugin-no-unsafe-innerhtml": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsafe-innerhtml/-/eslint-plugin-no-unsafe-innerhtml-1.0.16.tgz", + "integrity": "sha1-fQKHjI6b95FriINtWsEitC8VGTI=", + "requires": { + "eslint": "^3.7.1" + }, + "dependencies": { + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.0.0" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "requires": { + "once": "^1.3.0" + } + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "requires": { + "ajv": "^4.7.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "requires": { + "os-homedir": "^1.0.0" + } + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "requires": { + "mkdirp": "^0.5.1" + } + } + } + }, + "eslint-plugin-no-unsanitized": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-3.0.2.tgz", + "integrity": "sha512-JnwpoH8Sv4QOjrTDutENBHzSnyYtspdjtglYtqUtAHe6f6LLKqykJle+UwFPg23GGwt5hI3amS9CRDezW8GAww==" + }, + "eslint-plugin-security": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz", + "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==", + "requires": { + "safe-regex": "^1.1.0" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==" + }, + "espree": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==" + } + } + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "requires": { + "is-property": "^1.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "requires": { + "ini": "^1.3.4" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + } + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==" + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, + "import-fresh": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==" + }, + "is-my-json-valid": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", + "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + }, + "jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "requires": { + "core-js": "~2.3.0", + "es6-promise": "~3.0.2", + "lie": "~3.1.0", + "pako": "~1.0.2", + "readable-stream": "~2.0.6" + }, + "dependencies": { + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=" + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "requires": { + "package-json": "^4.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "requires": { + "immediate": "~3.0.5" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==" + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + } + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=" + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } + } + } + } + }, + "node-releases": { + "version": "1.1.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.36.tgz", + "integrity": "sha512-ggXhX6QGyJSjj3r+6ml2LqqC28XOWmKtpb+a15/Zpr9V3yoNazxJNlcQDS9bYaid5FReEWHEgToH1mwoUceWwg==", + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "nodemon": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.4.tgz", + "integrity": "sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==", + "requires": { + "chokidar": "^2.1.8", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + } + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "output-file-sync": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz", + "integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==", + "requires": { + "graceful-fs": "^4.1.11", + "is-plain-obj": "^1.1.0", + "mkdirp": "^0.5.1" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + } + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=" + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-transform": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "requires": { + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" + }, + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + } + } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "requires": { + "aproba": "^1.1.1" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=" + }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + }, + "dependencies": { + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" + } + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "requires": { + "semver": "^5.0.3" + } + }, + "serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "requires": { + "execa": "^0.7.0" + } + }, + "terser": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.6.tgz", + "integrity": "sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==", + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", + "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "serialize-javascript": "^2.1.2", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "requires": { + "debug": "^2.2.0" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "webpack": { + "version": "4.41.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz", + "integrity": "sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/wasm-edit": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.1", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==" + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "webpack-cli": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.9.tgz", + "integrity": "sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A==", + "requires": { + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "enhanced-resolve": "4.1.0", + "findup-sync": "3.0.0", + "global-modules": "2.0.0", + "import-local": "2.0.0", + "interpret": "1.2.0", + "supports-color": "6.1.0", + "v8-compile-cache": "2.0.3", + "yargs": "13.2.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "v8-compile-cache": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "requires": { + "errno": "~0.1.7" + } + }, + "worker-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", + "requires": { + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" + }, + "dependencies": { + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } + } + } + } + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detect-browser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.1.1.tgz", + "integrity": "sha512-5n2aWI57qC3kZaK4j2zYsG6L1LrxgLptGCNhMQgdKhVn6cSdcq43pp6xHPfTHG3TYM6myF4tIPWiZtfdVDgb9w==" + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + }, + "diff-sequences": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==" + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "electron-to-chromium": { + "version": "1.3.520", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.520.tgz", + "integrity": "sha512-q6H9E1sXDCjRHP+X06vcP+N0ki8ZvYoRPZfKnDuiRX10WWXxEHzKFVf4O9rBFMpuPtR3M+2KAdJnugJoBBp3Rw==", + "dev": true + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "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" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "requires": { + "stackframe": "^1.1.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escalade": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz", + "integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^6.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-config-airbnb-base": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz", + "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.7", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + } + } + }, + "eslint-plugin-no-unsafe-innerhtml": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsafe-innerhtml/-/eslint-plugin-no-unsafe-innerhtml-1.0.16.tgz", + "integrity": "sha1-fQKHjI6b95FriINtWsEitC8VGTI=", + "dev": true, + "requires": { + "eslint": "^3.7.1" + }, + "dependencies": { + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.0.0" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } + }, + "eslint-plugin-no-unsanitized": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-3.1.2.tgz", + "integrity": "sha512-KPShfliA3Uy9qqwQx35P1fwIOeJjZkb0FbMMUFztRYRposzaynsM8JCEb952fqkidROl1kpqY80uSvn+TcWkQQ==", + "dev": true + }, + "eslint-plugin-security": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz", + "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==", + "dev": true, + "requires": { + "safe-regex": "^1.1.0" + } + }, + "eslint-scope": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==" + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "expect": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", + "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "requires": { + "@jest/types": "^24.9.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.9.0" + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "requires": { + "bser": "2.1.1" + } + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + } + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.20.5", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz", + "integrity": "sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0" + } + }, + "jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", + "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.9.0" + }, + "dependencies": { + "jest-cli": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", + "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", + "dev": true, + "requires": { + "@jest/core": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^13.3.0" + } + } + } + }, + "jest-changed-files": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", + "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", + "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.9.0", + "@jest/types": "^24.9.0", + "babel-jest": "^24.9.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.9.0", + "jest-environment-node": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-jasmine2": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.9.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-docblock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", + "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", + "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "requires": { + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-environment-jsdom": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", + "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "requires": { + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", + "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", + "requires": { + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0" + } + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==" + }, + "jest-haste-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", + "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "requires": { + "@jest/types": "^24.9.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.9.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", + "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.9.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0", + "throat": "^4.0.0" + } + }, + "jest-junit": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-6.4.0.tgz", + "integrity": "sha512-GXEZA5WBeUich94BARoEUccJumhCgCerg7mXDFLxWwI2P7wL3Z7sGWk+53x343YdBLjiMR9aD/gYMVKO+0pE4Q==", + "dev": true, + "requires": { + "jest-validate": "^24.0.0", + "mkdirp": "^0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "jest-leak-detector": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", + "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "requires": { + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-matcher-utils": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", + "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", + "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "requires": { + "@jest/types": "^24.9.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==" + }, + "jest-resolve": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", + "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", + "requires": { + "@jest/types": "^24.9.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", + "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.9.0" + } + }, + "jest-runner": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", + "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.9.0", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.9.0", + "jest-jasmine2": "^24.9.0", + "jest-leak-detector": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.6.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + } + }, + "jest-runtime": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", + "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.9.0", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^13.3.0" + } + }, + "jest-serializer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==" + }, + "jest-snapshot": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", + "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "expect": "^24.9.0", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.9.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "jest-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", + "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "requires": { + "@jest/console": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/source-map": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "jest-validate": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", + "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", + "requires": { + "@jest/types": "^24.9.0", + "camelcase": "^5.3.1", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "leven": "^3.1.0", + "pretty-format": "^24.9.0" + } + }, + "jest-watcher": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", + "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "dev": true, + "requires": { + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.9.0", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "js2xmlparser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", + "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.3" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdoc": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.5.tgz", + "integrity": "sha512-SbY+i9ONuxSK35cgVHaI8O9senTE4CDYAmGSDJ5l3+sfe62Ff4gy96osy6OW84t4K4A8iGnMrlRrsSItSNp3RQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.8.11", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^0.8.2", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.10.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + } + } + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.0.2.tgz", + "integrity": "sha512-rmzlgFZiQPc6q4HDyK8s9Qb4oxBnI5sF61y/Co5PV0lc3q2bIuRsNdueVbhoSHdKM4fxeimphOAtfz47yjCfeA==", + "requires": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.7.0" + }, + "dependencies": { + "esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs=" + } + } + }, + "jsonpointer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", + "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "dev": true, + "requires": { + "leven": "^3.1.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", + "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "dev": true + }, + "marked": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", + "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" + }, + "node-notifier": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", + "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "node-releases": { + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "has": "^1.0.3" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true, + "optional": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "optional": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prompts": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.4" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.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==" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + } + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "requires": { + "util.promisify": "^1.0.0" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "regenerate": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + } + } + }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "rxjs": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", + "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" + }, + "static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "requires": { + "escodegen": "^1.8.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "store": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/store/-/store-2.0.12.tgz", + "integrity": "sha1-jFNOKguDH3K3X8XxEZhXxE711ZM=" + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", + "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^3.1.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "requires": { + "punycode": "^2.1.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "optional": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "requires": { + "makeerror": "1.0.x" + } + }, + "watchpack": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "webpack": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz", + "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "webpack-cli": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "worker-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", + "requires": { + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "xmlcreate": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", + "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/cvat-core/package.json b/cvat-core/package.json index b2f95a0c..ca65f045 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "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", "main": "babel.config.js", "scripts": { @@ -27,7 +27,7 @@ "eslint-plugin-security": "^1.4.0", "jest": "^24.8.0", "jest-junit": "^6.4.0", - "jsdoc": "^3.6.2", + "jsdoc": "^3.6.4", "webpack": "^4.31.0", "webpack-cli": "^3.3.2" }, diff --git a/cvat-core/src/annotation-formats.js b/cvat-core/src/annotation-formats.js index 641f7bc0..a248c9d7 100644 --- a/cvat-core/src/annotation-formats.js +++ b/cvat-core/src/annotation-formats.js @@ -15,6 +15,7 @@ name: initialData.name, format: initialData.ext, version: initialData.version, + enabled: initialData.enabled, }; Object.defineProperties(this, { @@ -48,6 +49,16 @@ */ 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, format: initialData.ext, version: initialData.version, + enabled: initialData.enabled, }; Object.defineProperties(this, { @@ -96,6 +108,16 @@ */ get: () => data.version, }, + enabled: { + /** + * @name enabled + * @type {string} + * @memberof module:API.cvat.classes.Loader + * @readonly + * @instance + */ + get: () => data.enabled, + }, }); } } diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 1d9fc0fc..ed338f6b 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -402,6 +402,7 @@ frame: Math.min.apply(null, Object.keys(keyframes).map((frame) => +frame)), shapes: Object.values(keyframes), group: 0, + source: objectStates[0].source, label_id: label.id, attributes: Object.keys(objectStates[0].attributes) .reduce((accumulator, attrID) => { @@ -763,6 +764,7 @@ points: [...state.points], type: state.shapeType, z_order: state.zOrder, + source: state.source, }); } else if (state.objectType === 'track') { constructed.tracks.push({ @@ -770,6 +772,7 @@ .filter((attr) => !labelAttributes[attr.spec_id].mutable), frame: state.frame, group: 0, + source: state.source, label_id: state.label.id, shapes: [{ attributes: attributes @@ -797,15 +800,17 @@ .concat(imported.tracks) .concat(imported.shapes); - this.history.do(HistoryActions.CREATED_OBJECTS, () => { - importedArray.forEach((object) => { - object.removed = true; - }); - }, () => { - importedArray.forEach((object) => { - object.removed = false; - }); - }, importedArray.map((object) => object.clientID), objectStates[0].frame); + if (objectStates.length) { + this.history.do(HistoryActions.CREATED_OBJECTS, () => { + importedArray.forEach((object) => { + object.removed = true; + }); + }, () => { + importedArray.forEach((object) => { + object.removed = false; + }); + }, importedArray.map((object) => object.clientID), objectStates[0].frame); + } return importedArray.map((value) => value.clientID); } diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 20d2e222..752670fe 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -15,6 +15,7 @@ } = require('./common'); const { colors, + Source, ObjectShape, ObjectType, AttributeType, @@ -156,8 +157,7 @@ if (type === AttributeType.NUMBER) { return +value >= +values[0] - && +value <= +values[1] - && !((+value - +values[0]) % +values[2]); + && +value <= +values[1]; } if (type === AttributeType.CHECKBOX) { @@ -184,6 +184,7 @@ this.removed = false; this.lock = false; this.color = color; + this.source = data.source; this.updated = Date.now(); this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { attributeAccumulator[attr.spec_id] = attr.value; @@ -333,7 +334,9 @@ const { width, height } = this.frameMeta[frame]; 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 = []; } } @@ -491,6 +494,7 @@ frame: this.frame, label_id: this.label.id, group: this.group, + source: this.source, }; } @@ -519,51 +523,67 @@ updated: this.updated, pinned: this.pinned, frame, + source: this.source, }; } _savePoints(points, frame) { const undoPoints = this.points; const redoPoints = points; + const undoSource = this.source; + const redoSource = Source.MANUAL; this.history.do(HistoryActions.CHANGED_POINTS, () => { this.points = undoPoints; + this.source = undoSource; this.updated = Date.now(); }, () => { this.points = redoPoints; + this.source = redoSource; this.updated = Date.now(); }, [this.clientID], frame); + this.source = Source.MANUAL; this.points = points; } _saveOccluded(occluded, frame) { const undoOccluded = this.occluded; const redoOccluded = occluded; + const undoSource = this.source; + const redoSource = Source.MANUAL; this.history.do(HistoryActions.CHANGED_OCCLUDED, () => { this.occluded = undoOccluded; + this.source = undoSource; this.updated = Date.now(); }, () => { this.occluded = redoOccluded; + this.source = redoSource; this.updated = Date.now(); }, [this.clientID], frame); + this.source = Source.MANUAL; this.occluded = occluded; } _saveZOrder(zOrder, frame) { const undoZOrder = this.zOrder; const redoZOrder = zOrder; + const undoSource = this.source; + const redoSource = Source.MANUAL; this.history.do(HistoryActions.CHANGED_ZORDER, () => { this.zOrder = undoZOrder; + this.source = undoSource; this.updated = Date.now(); }, () => { this.zOrder = redoZOrder; + this.source = redoSource; this.updated = Date.now(); }, [this.clientID], frame); + this.source = Source.MANUAL; this.zOrder = zOrder; } @@ -658,6 +678,7 @@ frame: this.frame, label_id: this.label.id, group: this.group, + source: this.source, attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { if (!labelAttributes[attrId].mutable) { attributeAccumulator.push({ @@ -725,6 +746,7 @@ last, }, frame, + source: this.source, }; } @@ -910,13 +932,14 @@ }, [this.clientID], frame); } - _appendShapeActionToHistory(actionType, frame, undoShape, redoShape) { + _appendShapeActionToHistory(actionType, frame, undoShape, redoShape, undoSource, redoSource) { this.history.do(actionType, () => { if (!undoShape) { delete this.shapes[frame]; } else { this.shapes[frame] = undoShape; } + this.source = undoSource; this.updated = Date.now(); }, () => { if (!redoShape) { @@ -924,6 +947,7 @@ } else { this.shapes[frame] = redoShape; } + this.source = redoSource; this.updated = Date.now(); }, [this.clientID], frame); } @@ -931,6 +955,8 @@ _savePoints(points, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = Source.MANUAL; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const redoShape = wasKeyframe ? { ...this.shapes[frame], points } : { frame, @@ -942,17 +968,22 @@ }; this.shapes[frame] = redoShape; + this.source = Source.MANUAL; this._appendShapeActionToHistory( HistoryActions.CHANGED_POINTS, frame, undoShape, redoShape, + undoSource, + redoSource, ); } _saveOutside(frame, outside) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = Source.MANUAL; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const redoShape = wasKeyframe ? { ...this.shapes[frame], outside } : { frame, @@ -964,17 +995,22 @@ }; this.shapes[frame] = redoShape; + this.source = Source.MANUAL; this._appendShapeActionToHistory( HistoryActions.CHANGED_OUTSIDE, frame, undoShape, redoShape, + undoSource, + redoSource, ); } _saveOccluded(occluded, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = Source.MANUAL; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const redoShape = wasKeyframe ? { ...this.shapes[frame], occluded } : { frame, @@ -986,17 +1022,22 @@ }; this.shapes[frame] = redoShape; + this.source = Source.MANUAL; this._appendShapeActionToHistory( HistoryActions.CHANGED_OCCLUDED, frame, undoShape, redoShape, + undoSource, + redoSource, ); } _saveZOrder(zOrder, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = Source.MANUAL; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const redoShape = wasKeyframe ? { ...this.shapes[frame], zOrder } : { frame, @@ -1008,11 +1049,14 @@ }; this.shapes[frame] = redoShape; + this.source = Source.MANUAL; this._appendShapeActionToHistory( HistoryActions.CHANGED_ZORDER, frame, undoShape, redoShape, + undoSource, + redoSource, ); } @@ -1025,6 +1069,8 @@ return; } + const undoSource = this.source; + const redoSource = Source.MANUAL; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const redoShape = keyframe ? { frame, @@ -1033,8 +1079,10 @@ outside: current.outside, occluded: current.occluded, attributes: {}, + source: current.source, } : undefined; + this.source = Source.MANUAL; if (redoShape) { this.shapes[frame] = redoShape; } else { @@ -1046,6 +1094,8 @@ frame, undoShape, redoShape, + undoSource, + redoSource, ); } @@ -1163,6 +1213,7 @@ frame: this.frame, label_id: this.label.id, group: this.group, + source: this.source, attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { attributeAccumulator.push({ spec_id: attrId, @@ -1193,6 +1244,7 @@ color: this.color, updated: this.updated, frame, + source: this.source, }; } @@ -1534,13 +1586,12 @@ } interpolatePosition(leftPosition, rightPosition, offset) { - const positionOffset = leftPosition.points.map((point, index) => ( rightPosition.points[index] - point - )) + )); return { - points: leftPosition.points.map((point ,index) => ( + points: leftPosition.points.map((point, index) => ( point + positionOffset[index] * offset )), occluded: leftPosition.occluded, @@ -1556,385 +1607,274 @@ } interpolatePosition(leftPosition, rightPosition, offset) { - function findBox(points) { - 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]; - } - + if (offset === 0) { return { - xmin, - ymin, - xmax, - ymax, + points: [...leftPosition.points], + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, }; } - function normalize(points, box) { - const normalized = []; - const width = box.xmax - box.xmin; - const height = box.ymax - box.ymin; + function toArray(points) { + return points.reduce((acc, val) => { + acc.push(val.x, val.y); + return acc; + }, []); + } - for (let i = 0; i < points.length; i += 2) { - normalized.push( - (points[i] - box.xmin) / width, - (points[i + 1] - box.ymin) / height, - ); - } + function toPoints(array) { + return array.reduce((acc, _, index) => { + if (index % 2) { + 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) { - const denormalized = []; - const width = box.xmax - box.xmin; - const height = box.ymax - box.ymin; + function curveToOffsetVec(points, length) { + const offsetVector = [0]; // with initial value + let accumulatedLength = 0; - for (let i = 0; i < points.length; i += 2) { - denormalized.push( - points[i] * width + box.xmin, - points[i + 1] * height + box.ymin, - ); - } + points.slice(1).forEach((_, index) => { + const dx = points[index + 1].x - points[index].x; + const dy = points[index + 1].y - points[index].y; + accumulatedLength += Math.sqrt(dx ** 2 + dy ** 2); + offsetVector.push(accumulatedLength / length); + }); - return denormalized; + return offsetVector; } - function toPoints(array) { - const points = []; - for (let i = 0; i < array.length; i += 2) { - points.push({ - x: array[i], - y: array[i + 1], - }); + function findNearestPair(value, curve) { + let minimum = [0, Math.abs(value - curve[0])]; + for (let i = 1; i < curve.length; i++) { + const distance = Math.abs(value - curve[i]); + if (distance < minimum[1]) { + minimum = [i, distance]; + } } - return points; + return minimum[0]; } - function toArray(points) { - const array = []; - for (const point of points) { - array.push(point.x, point.y); + function matchLeftRight(leftCurve, rightCurve) { + const matching = {}; + for (let i = 0; i < leftCurve.length; i++) { + matching[i] = [findNearestPair(leftCurve[i], rightCurve)]; } - return array; + return matching; } - function computeDistances(source, target) { - const distances = {}; - for (let i = 0; i < source.length; i++) { - distances[i] = distances[i] || {}; - for (let j = 0; j < target.length; j++) { - const dx = source[i].x - target[j].x; - const dy = source[i].y - target[j].y; + function matchRightLeft(leftCurve, rightCurve, leftRightMatching) { + const matchedRightPoints = Object.values(leftRightMatching).flat(); + const unmatchedRightPoints = rightCurve.map((_, index) => index) + .filter((index) => !matchedRightPoints.includes(index)); + const updatedMatching = { ...leftRightMatching }; - 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) { - for (const key of Object.keys(mapping)) { - if (mapping[key].distance > threshold) { - delete mapping[key]; + function reduceInterpolation(interpolatedPoints, matching, leftPoints, rightPoints) { + function averagePoint(points) { + let sumX = 0; + let sumY = 0; + for (const point of points) { + sumX += point.x; + sumY += point.y; } - } - } - // https://en.wikipedia.org/wiki/Stable_marriage_problem - // TODO: One of important part of the algorithm is to correctly match - // "corner" points. Thus it is possible for each of such point calculate - // 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]); + return { + x: sumX / points.length, + y: sumY / points.length, + }; } - // Start alghoritm with max N^2 complexity - const womenMaybe = {}; // id woman:id man,distance - const menBusy = {}; // id man:boolean - 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; - } + function computeDistance(point1, point2) { + return Math.sqrt( + ((point1.x - point2.x) ** 2) + ((point1.y - point2.y) ** 2), + ); + } - const woman = menPreferences[man][prefIndex]; - const distance = distances[man][woman]; + function minimizeSegment(baseLength, N, startInterpolated, stopInterpolated) { + 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 (woman in womenMaybe && womenMaybe[woman].distance > distance) { - // A woman got better offer - const prevChoice = womenMaybe[woman].value; - delete womenMaybe[woman]; - delete menBusy[prevChoice]; + if (distance >= threshold) { + minimized.push(interpolatedPoints[i]); + latestPushed = i; } + } - if (!(woman in womenMaybe)) { - womenMaybe[woman] = { - value: man, - distance, - }; + minimized.push(interpolatedPoints[stopInterpolated]); + + if (minimized.length === 2) { + const distance = computeDistance( + interpolatedPoints[startInterpolated], + interpolatedPoints[stopInterpolated], + ); - menBusy[man] = true; + if (distance < threshold) { + return [averagePoint(minimized)]; } } - prefIndex++; + return minimized; } - const result = {}; - for (const woman of Object.keys(womenMaybe)) { - result[womenMaybe[woman].value] = { - value: woman, - distance: womenMaybe[woman].distance, - }; + const reduced = []; + const interpolatedIndexes = {}; + let accumulated = 0; + for (let i = 0; i < leftPoints.length; i++) { + // 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) { - function sumEdges(points) { - let result = 0; - 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; + if (startInterpolated === stopInterpolated) { + reduced.push(interpolatedPoints[startInterpolated]); + return; } - // Corner case when work with one point - // Mapping in this case can't be wrong - if (!result) { - return Number.MAX_SAFE_INTEGER; - } + const baseLength = curveLength(leftPoints.slice(start, stop + 1)); + const N = stop - start + 1; - return result; + reduced.push( + ...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated), + ); } - function computeDeviation(points, average) { - let result = 0; - 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 += Math.pow(distance - average, 2); - } - - return result; - } + function rightSegment(leftPoint) { + const start = matching[leftPoint][0]; + const [stop] = matching[leftPoint].slice(-1); + const startInterpolated = interpolatedIndexes[leftPoint][0]; + const [stopInterpolated] = interpolatedIndexes[leftPoint].slice(-1); + const baseLength = curveLength(rightPoints.slice(start, stop + 1)); + const N = stop - start + 1; - const processedSource = []; - const processedTarget = []; - - 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; + reduced.push( + ...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated), + ); } - // const receivingOrder = Object.keys(mapping).map(x => +x).sort((a,b) => a - b); - const receivingOrder = this.appendMapping(mapping, source, target); + let previousOpened = null; + 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) { - processedSource.push(source[pointIdx]); - processedTarget.push(target[mapping[pointIdx]]); + // right segment found + rightSegment(i); + } } - return [processedSource, processedTarget]; - } + // check if there is an opened segment + if (previousOpened !== null) { + leftSegment(previousOpened, leftPoints.length - 1); + } - if (offset === 0) { - return { - points: [...leftPosition.points], - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; + return reduced; } - let leftBox = findBox(leftPosition.points); - let rightBox = findBox(rightPosition.points); - - // Sometimes (if shape has one point or shape is line), - // We can get box with zero area - // Next computation will be with NaN in this case - // 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; - } + // the algorithm below is based on fact that both left and right + // polyshapes have the same start point and the same draw direction + const leftPoints = toPoints(leftPosition.points); + const rightPoints = toPoints(rightPosition.points); + const leftOffsetVec = curveToOffsetVec(leftPoints, curveLength(leftPoints)); + const rightOffsetVec = curveToOffsetVec(rightPoints, curveLength(rightPoints)); - const leftPoints = toPoints(normalize(leftPosition.points, leftBox)); - const rightPoints = toPoints(normalize(rightPosition.points, rightBox)); + const matching = matchLeftRight(leftOffsetVec, rightOffsetVec); + const completedMatching = matchRightLeft( + leftOffsetVec, rightOffsetVec, matching, + ); - let newLeftPoints = []; - let newRightPoints = []; - if (leftPoints.length > rightPoints.length) { - const [ - processedRight, - processedLeft, - ] = getMapping.call(this, rightPoints, leftPoints); - newLeftPoints = processedLeft; - newRightPoints = processedRight; - } else { - const [ - processedLeft, - processedRight, - ] = getMapping.call(this, leftPoints, rightPoints); - newLeftPoints = processedLeft; - newRightPoints = processedRight; - } + const interpolatedPoints = Object.keys(completedMatching) + .map((leftPointIdx) => +leftPointIdx).sort((a, b) => a - b) + .reduce((acc, leftPointIdx) => { + const leftPoint = leftPoints[leftPointIdx]; + for (const rightPointIdx of completedMatching[leftPointIdx]) { + const rightPoint = rightPoints[rightPointIdx]; + acc.push({ + x: leftPoint.x + (rightPoint.x - leftPoint.x) * offset, + y: leftPoint.y + (rightPoint.y - leftPoint.y) * offset, + }); + } - const absoluteLeftPoints = denormalize(toArray(newLeftPoints), leftBox); - const absoluteRightPoints = denormalize(toArray(newRightPoints), rightBox); + return acc; + }, []); - const interpolation = []; - for (let i = 0; i < absoluteLeftPoints.length; i++) { - interpolation.push(absoluteLeftPoints[i] + ( - absoluteRightPoints[i] - absoluteLeftPoints[i]) * offset); - } + const reducedPoints = reduceInterpolation( + interpolatedPoints, + completedMatching, + leftPoints, + rightPoints, + ); return { - points: interpolation, + points: toArray(reducedPoints), occluded: leftPosition.occluded, outside: leftPosition.outside, 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 { @@ -1945,6 +1885,26 @@ 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 { @@ -1965,6 +1925,27 @@ 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 { @@ -1978,13 +1959,12 @@ } interpolatePosition(leftPosition, rightPosition, offset) { - const positionOffset = leftPosition.points.map((point, index) => ( rightPosition.points[index] - point - )) + )); return { - points: leftPosition.points.map((point ,index) => ( + points: leftPosition.points.map((point, index) => ( point + positionOffset[index] * offset )), occluded: leftPosition.occluded, diff --git a/cvat-core/src/annotations-saver.js b/cvat-core/src/annotations-saver.js index 42a07ecb..779db776 100644 --- a/cvat-core/src/annotations-saver.js +++ b/cvat-core/src/annotations-saver.js @@ -104,7 +104,7 @@ const keys = ['id', 'label_id', 'group', 'frame', 'occluded', 'z_order', 'points', 'type', 'shapes', - 'attributes', 'value', 'spec_id', 'outside']; + 'attributes', 'value', 'spec_id', 'source', 'outside']; // Find created and updated objects for (const type of Object.keys(exported)) { diff --git a/cvat-core/src/annotations.js b/cvat-core/src/annotations.js index e2f452a7..9d7eadbf 100644 --- a/cvat-core/src/annotations.js +++ b/cvat-core/src/annotations.js @@ -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) { const sessionType = session instanceof Task ? 'task' : 'job'; const cache = getCache(sessionType); @@ -365,5 +374,6 @@ redoActions, clearActions, getActions, + closeSession, }; })(); diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js index da83fcac..5fc7958c 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019 Intel Corporation +* Copyright (C) 2019-2020 Intel Corporation * SPDX-License-Identifier: MIT */ @@ -12,6 +12,7 @@ (() => { const PluginRegistry = require('./plugins'); const serverProxy = require('./server-proxy'); + const lambdaManager = require('./lambda-manager'); const { isBoolean, isInteger, @@ -20,10 +21,7 @@ checkFilter, } = require('./common'); - const { - TaskStatus, - TaskMode, - } = require('./enums'); + const { TaskStatus, TaskMode } = require('./enums'); const User = require('./user'); const { AnnotationFormats } = require('./annotation-formats.js'); @@ -54,6 +52,13 @@ cvat.plugins.list.implementation = PluginRegistry.list; cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); + cvat.lambda.list.implementation = lambdaManager.list.bind(lambdaManager); + cvat.lambda.run.implementation = lambdaManager.run.bind(lambdaManager); + cvat.lambda.call.implementation = lambdaManager.call.bind(lambdaManager); + cvat.lambda.cancel.implementation = lambdaManager.cancel.bind(lambdaManager); + cvat.lambda.listen.implementation = lambdaManager.listen.bind(lambdaManager); + cvat.lambda.requests.implementation = lambdaManager.requests.bind(lambdaManager); + cvat.server.about.implementation = async () => { const result = await serverProxy.server.about(); return result; @@ -76,8 +81,10 @@ cvat.server.register.implementation = async (username, firstName, lastName, email, password1, password2, userConfirmations) => { - await serverProxy.server.register(username, firstName, lastName, email, - password1, password2, userConfirmations); + const user = await serverProxy.server.register(username, firstName, + lastName, email, password1, password2, userConfirmations); + + return new User(user); }; cvat.server.login.implementation = async (username, password) => { @@ -88,6 +95,10 @@ await serverProxy.server.logout(); }; + cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => { + await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2); + }; + cvat.server.authorized.implementation = async () => { const result = await serverProxy.server.authorized(); return result; diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 0e04c5c8..9d040683 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -20,6 +20,7 @@ function build() { const Statistics = require('./statistics'); const { Job, Task } = require('./session'); const { Attribute, Label } = require('./labels'); + const MLModel = require('./ml-model'); const { ShareFileType, @@ -30,7 +31,9 @@ function build() { ObjectShape, LogType, HistoryActions, + RQStatus, colors, + Source, } = require('./enums'); const { @@ -127,10 +130,10 @@ function build() { * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ServerError} */ - async userAgreements() { - const result = await PluginRegistry - .apiWrapper(cvat.server.userAgreements); - return result; + async userAgreements() { + const result = await PluginRegistry + .apiWrapper(cvat.server.userAgreements); + return result; }, /** @@ -145,10 +148,19 @@ function build() { * @param {string} password1 A 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 + * @returns {Object} response data * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ServerError} */ - async register(username, firstName, lastName, email, password1, password2, userConfirmations) { + async register( + username, + firstName, + lastName, + email, + password1, + password2, + userConfirmations, + ) { const result = await PluginRegistry .apiWrapper(cvat.server.register, username, firstName, lastName, email, password1, password2, userConfirmations); @@ -182,6 +194,19 @@ function build() { .apiWrapper(cvat.server.logout); return result; }, + /** + * Method allows to change user password + * @method changePassword + * @async + * @memberof module:API.cvat.server + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + */ + async changePassword(oldPassword, newPassword1, newPassword2) { + const result = await PluginRegistry + .apiWrapper(cvat.server.changePassword, oldPassword, newPassword1, newPassword2); + return result; + }, /** * Method allows to know whether you are authorized on the server * @method authorized @@ -423,6 +448,119 @@ function build() { return result; }, }, + /** + * Namespace is used for serverless functions management (mainly related with DL models) + * @namespace lambda + * @memberof module:API.cvat + */ + lambda: { + /** + * Method returns list of available serverless models + * @method list + * @async + * @memberof module:API.cvat.lambda + * @returns {module:API.cvat.classes.MLModel[]} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async list() { + const result = await PluginRegistry + .apiWrapper(cvat.lambda.list); + return result; + }, + + /** + * Run long-time request for a function on a specific task + * @method run + * @async + * @memberof module:API.cvat.lambda + * @param {module:API.cvat.classes.Task} task task to be annotated + * @param {module:API.cvat.classes.MLModel} model model used to get annotation + * @param {object} [args] extra arguments + * @returns {string} requestID + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async run(task, model, args) { + const result = await PluginRegistry + .apiWrapper(cvat.lambda.run, task, model, args); + return result; + }, + + /** + * Run short-time request for a function on a specific task + * @method call + * @async + * @memberof module:API.cvat.lambda + * @param {module:API.cvat.classes.Task} task task to be annotated + * @param {module:API.cvat.classes.MLModel} model model used to get annotation + * @param {object} [args] extra arguments + * @returns {string} requestID + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async call(task, model, args) { + const result = await PluginRegistry + .apiWrapper(cvat.lambda.call, task, model, args); + return result; + }, + + /** + * Cancel running of a serverless function for a specific task + * @method cancel + * @async + * @memberof module:API.cvat.lambda + * @param {string} requestID + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async cancel(requestID) { + const result = await PluginRegistry + .apiWrapper(cvat.lambda.cancel, requestID); + return result; + }, + + /** + * @callback onRequestStatusChange + * @param {string} status + * @param {number} progress + * @param {string} [message] + * @global + */ + /** + * Listen for a specific request + * @method listen + * @async + * @memberof module:API.cvat.lambda + * @param {string} requestID + * @param {onRequestStatusChange} onChange + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async listen(requestID, onChange) { + const result = await PluginRegistry + .apiWrapper(cvat.lambda.listen, requestID, onChange); + return result; + }, + + /** + * Get active lambda requests + * @method requests + * @async + * @memberof module:API.cvat.lambda + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async requests() { + const result = await PluginRegistry + .apiWrapper(cvat.lambda.requests); + return result; + }, + }, /** * Namespace to working with logs * @namespace logger @@ -530,7 +668,9 @@ function build() { ObjectShape, LogType, HistoryActions, + RQStatus, colors, + Source, }, /** * Namespace is used for access to exceptions @@ -559,6 +699,7 @@ function build() { Label, Statistics, ObjectState, + MLModel, }, }; @@ -567,6 +708,7 @@ function build() { cvat.jobs = Object.freeze(cvat.jobs); cvat.users = Object.freeze(cvat.users); cvat.plugins = Object.freeze(cvat.plugins); + cvat.lambda = Object.freeze(cvat.lambda); cvat.client = Object.freeze(cvat.client); cvat.enums = Object.freeze(cvat.enums); diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index 545eb122..c7b6c595 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -34,6 +34,26 @@ COMPLETED: 'completed', }); + /** + * List of RQ statuses + * @enum {string} + * @name RQStatus + * @memberof module:API.cvat.enums + * @property {string} QUEUED 'queued' + * @property {string} STARTED 'started' + * @property {string} FINISHED 'finished' + * @property {string} FAILED 'failed' + * @property {string} UNKNOWN 'unknown' + * @readonly + */ + const RQStatus = Object.freeze({ + QUEUED: 'queued', + STARTED: 'started', + FINISHED: 'finished', + FAILED: 'failed', + UNKNOWN: 'unknown', + }); + /** * Task modes * @enum {string} @@ -104,6 +124,20 @@ 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 * @enum {string} @@ -190,6 +224,7 @@ * @property {string} CHANGED_LOCK Changed lock * @property {string} CHANGED_COLOR Changed color * @property {string} CHANGED_HIDDEN Changed hidden + * @property {string} CHANGED_SOURCE Changed source * @property {string} MERGED_OBJECTS Merged objects * @property {string} SPLITTED_TRACK Splitted track * @property {string} GROUPED_OBJECTS Grouped objects @@ -209,6 +244,7 @@ CHANGED_PINNED: 'Changed pinned', CHANGED_COLOR: 'Changed color', CHANGED_HIDDEN: 'Changed hidden', + CHANGED_SOURCE: 'Changed source', MERGED_OBJECTS: 'Merged objects', SPLITTED_TRACK: 'Splitted track', GROUPED_OBJECTS: 'Grouped objects', @@ -216,6 +252,18 @@ REMOVED_OBJECT: 'Removed object', }); + /** + * Enum string values. + * @name ModelType + * @memberof module:API.cvat.enums + * @enum {string} + */ + const ModelType = { + DETECTOR: 'detector', + INTERACTOR: 'interactor', + TRACKER: 'tracker', + }; + /** * Array of hex colors * @name colors @@ -224,10 +272,11 @@ * @readonly */ const colors = [ - '#FF355E', '#E936A7', '#FD5B78', '#FF007C', '#FF00CC', '#66FF66', - '#50BFE6', '#CCFF00', '#FFFF66', '#FF9966', '#FF6037', '#FFCC33', - '#AAF0D1', '#FF3855', '#FFF700', '#A7F432', '#FF5470', '#FAFA37', - '#FF7A00', '#FF9933', '#AFE313', '#00CC99', '#FF5050', '#733380', + '#33ddff', '#fa3253', '#34d1b7', '#ff007c', '#ff6037', '#ddff33', + '#24b353', '#b83df5', '#66ff66', '#32b7fa', '#ffcc33', '#83e070', + '#fafa37', '#5986b3', '#8c78f0', '#ff6a4d', '#f078f0', '#2a7dd1', + '#b25050', '#cc3366', '#cc9933', '#aaf0d1', '#ff00cc', '#3df53d', + '#fa32b7', '#fa7dbb', '#ff355e', '#f59331', '#3d3df5', '#733380', ]; module.exports = { @@ -238,7 +287,10 @@ ObjectType, ObjectShape, LogType, + ModelType, HistoryActions, + RQStatus, colors, + Source, }; })(); diff --git a/cvat-core/src/frames.js b/cvat-core/src/frames.js index 66145a06..0e95d42d 100644 --- a/cvat-core/src/frames.js +++ b/cvat-core/src/frames.js @@ -605,10 +605,18 @@ }; } + function clear(taskID) { + if (taskID in frameDataCache) { + frameDataCache[taskID].frameBuffer.clear(); + delete frameDataCache[taskID]; + } + } + module.exports = { FrameData, getFrame, getRanges, getPreview, + clear, }; })(); diff --git a/cvat-core/src/labels.js b/cvat-core/src/labels.js index fa92a611..ec2c0cbb 100644 --- a/cvat-core/src/labels.js +++ b/cvat-core/src/labels.js @@ -10,7 +10,6 @@ (() => { const { AttributeType, - colors, } = require('./enums'); const { ArgumentError } = require('./exceptions'); @@ -150,9 +149,6 @@ } } - if (typeof (data.id) !== 'undefined') { - data.color = colors[data.id % colors.length]; - } data.attributes = []; if (Object.prototype.hasOwnProperty.call(initialData, 'attributes') @@ -193,10 +189,10 @@ color: { get: () => data.color, set: (color) => { - if (colors.includes(color)) { + if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) { data.color = color; } else { - throw new ArgumentError('Trying to set unknown color'); + throw new ArgumentError('Trying to set wrong color format'); } }, }, @@ -217,6 +213,7 @@ const object = { name: this.name, attributes: [...this.attributes.map((el) => el.toJSON())], + color: this.color, }; if (typeof (this.id) !== 'undefined') { diff --git a/cvat-core/src/lambda-manager.js b/cvat-core/src/lambda-manager.js new file mode 100644 index 00000000..4b936cd2 --- /dev/null +++ b/cvat-core/src/lambda-manager.js @@ -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(); diff --git a/cvat-core/src/ml-model.js b/cvat-core/src/ml-model.js new file mode 100644 index 00000000..4169be8e --- /dev/null +++ b/cvat-core/src/ml-model.js @@ -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; diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js index 93d17d48..64cad384 100644 --- a/cvat-core/src/object-state.js +++ b/cvat-core/src/object-state.js @@ -3,6 +3,8 @@ * SPDX-License-Identifier: MIT */ +const { Source } = require('./enums'); + /* global require:false */ @@ -22,7 +24,7 @@ *
Necessary fields: objectType, shapeType, frame, updated, group *
Optional fields: keyframes, clientID, serverID *
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) { const data = { @@ -39,6 +41,7 @@ color: null, hidden: null, pinned: null, + source: Source.MANUAL, keyframes: serialized.keyframes, group: serialized.group, updated: serialized.updated, @@ -109,6 +112,16 @@ */ get: () => data.shapeType, }, + source: { + /** + * @name source + * @type {module:API.cvat.enums.Source} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.source, + }, clientID: { /** * @name clientID @@ -344,6 +357,9 @@ this.label = serialized.label; this.lock = serialized.lock; + if ([Source.MANUAL, Source.AUTO].includes(serialized.source)) { + data.source = serialized.source; + } if (typeof (serialized.zOrder) === 'number') { this.zOrder = serialized.zOrder; } diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index 5b685114..4a0c9ebd 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -162,7 +162,6 @@ response = await Axios.get(`${backendAPI}/restrictions/user-agreements`, { proxy: config.proxy, }); - } catch (errorData) { throw generateError(errorData); } @@ -170,7 +169,15 @@ return response.data; } - async function register(username, firstName, lastName, email, password1, password2, confirmations) { + async function register( + username, + firstName, + lastName, + email, + password1, + password2, + confirmations, + ) { let response = null; try { const data = JSON.stringify({ @@ -239,6 +246,24 @@ Axios.defaults.headers.common.Authorization = ''; } + async function changePassword(oldPassword, newPassword1, newPassword2) { + try { + const data = JSON.stringify({ + old_password: oldPassword, + new_password1: newPassword1, + new_password2:newPassword2, + }); + await Axios.post(`${config.backendAPI}/auth/password/change`, data, { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + } + async function authorized() { try { await module.exports.users.getSelf(); @@ -662,6 +687,96 @@ } } + async function getLambdaFunctions() { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/lambda/functions`, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } + } + + async function runLambdaRequest(body) { + const { backendAPI } = config; + + try { + const response = await Axios.post(`${backendAPI}/lambda/requests`, + JSON.stringify(body), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + + return response.data; + } catch (errorData) { + throw generateError(errorData); + } + } + + async function callLambdaFunction(funId, body) { + const { backendAPI } = config; + + try { + const response = await Axios.post(`${backendAPI}/lambda/functions/${funId}`, + JSON.stringify(body), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + + return response.data; + } catch (errorData) { + throw generateError(errorData); + } + } + + async function getLambdaRequests() { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/lambda/requests`, { + proxy: config.proxy, + }); + + return response.data; + } catch (errorData) { + throw generateError(errorData); + } + } + + async function getRequestStatus(requestID) { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/lambda/requests/${requestID}`, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } + } + + async function cancelLambdaRequest(requestId) { + const { backendAPI } = config; + + try { + await Axios.delete( + `${backendAPI}/lambda/requests/${requestId}`, { + method: 'DELETE', + }, + ); + } catch (errorData) { + throw generateError(errorData); + } + } + Object.defineProperties(this, Object.freeze({ server: { value: Object.freeze({ @@ -671,6 +786,7 @@ exception, login, logout, + changePassword, authorized, register, request: serverRequest, @@ -731,6 +847,18 @@ }), writable: false, }, + + lambda: { + value: Object.freeze({ + list: getLambdaFunctions, + status: getRequestStatus, + requests: getLambdaRequests, + run: runLambdaRequest, + call: callLambdaFunction, + cancel: cancelLambdaRequest, + }), + writable: false, + }, })); } } diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index 2af76d13..6a994e0a 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -11,7 +11,12 @@ const PluginRegistry = require('./plugins'); const loggerStorage = require('./logger-storage'); const serverProxy = require('./server-proxy'); - const { getFrame, getRanges, getPreview } = require('./frames'); + const { + getFrame, + getRanges, + getPreview, + clear: clearFrames, + } = require('./frames'); const { ArgumentError } = require('./exceptions'); const { TaskStatus } = require('./enums'); const { Label } = require('./labels'); @@ -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 save @@ -1372,6 +1392,7 @@ redoActions, clearActions, getActions, + closeSession, } = require('./annotations'); buildDublicatedAPI(Job.prototype); @@ -1576,6 +1597,16 @@ 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) { // TODO: Add ability to change an owner and an assignee if (typeof (this.id) !== 'undefined') { diff --git a/cvat-core/src/user.js b/cvat-core/src/user.js index 62221c67..555ea83d 100644 --- a/cvat-core/src/user.js +++ b/cvat-core/src/user.js @@ -23,6 +23,7 @@ is_staff: null, is_superuser: null, is_active: null, + email_verification_required: null, }; for (const property in data) { @@ -143,6 +144,16 @@ */ get: () => data.is_active, }, + isVerified: { + /** + * @name isVerified + * @type {boolean} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => !data.email_verification_required, + }, })); } } diff --git a/cvat-core/webpack.config.js b/cvat-core/webpack.config.js index 6e16f46a..99b9e64d 100644 --- a/cvat-core/webpack.config.js +++ b/cvat-core/webpack.config.js @@ -30,10 +30,12 @@ const webConfig = { target: 'web', mode: 'production', devtool: 'source-map', - entry: './src/api.js', + entry: { + 'cvat-core': './src/api.js', + }, output: { path: path.resolve(__dirname, 'dist'), - filename: 'cvat-core.min.js', + filename: '[name].[contenthash].min.js', library: 'cvat', libraryTarget: 'window', }, @@ -58,7 +60,7 @@ const webConfig = { loader: 'worker-loader', options: { publicPath: '/static/engine/js/3rdparty/', - name: '[name].js', + name: '[name].[contenthash].js', }, }, }, { @@ -68,7 +70,7 @@ const webConfig = { loader: 'worker-loader', options: { publicPath: '/static/engine/js/', - name: '[name].js', + name: '[name].[contenthash].js', }, }, }, diff --git a/cvat-data/package-lock.json b/cvat-data/package-lock.json index 4a96cca3..f392ce8e 100644 --- a/cvat-data/package-lock.json +++ b/cvat-data/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-data", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-data/package.json b/cvat-data/package.json index eb78de78..5c42155e 100644 --- a/cvat-data/package.json +++ b/cvat-data/package.json @@ -1,6 +1,6 @@ { "name": "cvat-data", - "version": "1.0.0", + "version": "1.0.1", "description": "", "main": "src/js/cvat-data.js", "devDependencies": { diff --git a/cvat-data/src/js/cvat-data.js b/cvat-data/src/js/cvat-data.js index 8484e9ac..86a0a6d0 100644 --- a/cvat-data/src/js/cvat-data.js +++ b/cvat-data/src/js/cvat-data.js @@ -295,7 +295,26 @@ class FrameProvider { worker.terminate(); }; - worker.onmessage = (event) => { + worker.onmessage = async (event) => { + if (event.data.isRaw) { + // safary doesn't support createImageBitmap + // there is a way to polyfill it with using document.createElement + // but document.createElement doesn't work in worker + // so, we get raw data and decode it here, no other way + + const createImageBitmap = async function(blob) { + return new Promise((resolve,reject) => { + let img = document.createElement('img'); + img.addEventListener('load', function() { + resolve(this); + }); + img.src = URL.createObjectURL(blob); + }); + }; + + event.data.data = await createImageBitmap(event.data.data); + } + this._frames[event.data.index] = event.data.data; if (this._decodingBlocks[`${start}:${end}`].resolveCallback) { diff --git a/cvat-data/src/js/unzip_imgs.worker.js b/cvat-data/src/js/unzip_imgs.worker.js index f704e098..68d0b2a5 100644 --- a/cvat-data/src/js/unzip_imgs.worker.js +++ b/cvat-data/src/js/unzip_imgs.worker.js @@ -20,13 +20,22 @@ onmessage = (e) => { const fileIndex = index++; if (fileIndex <= end) { _zip.file(relativePath).async('blob').then((fileData) => { - createImageBitmap(fileData).then((img) => { + if (self.createImageBitmap) { + createImageBitmap(fileData).then((img) => { + postMessage({ + fileName: relativePath, + index: fileIndex, + data: img, + }); + }); + } else { postMessage({ fileName: relativePath, index: fileIndex, - data: img, + data: fileData, + isRaw: true, }); - }); + } }); } }); diff --git a/cvat-data/webpack.config.js b/cvat-data/webpack.config.js index 1b3d09f7..298f6747 100644 --- a/cvat-data/webpack.config.js +++ b/cvat-data/webpack.config.js @@ -9,10 +9,12 @@ const CopyPlugin = require('copy-webpack-plugin'); const cvatData = { target: 'web', mode: 'production', - entry: './src/js/cvat-data.js', + entry: { + 'cvat-data': './src/js/cvat-data.js', + }, output: { path: path.resolve(__dirname, 'dist'), - filename: 'cvat-data.min.js', + filename: '[name].[contenthash].min.js', library: 'cvatData', libraryTarget: 'window', }, @@ -39,7 +41,7 @@ const cvatData = { loader: 'worker-loader', options: { publicPath: '/', - name: '[name].js', + name: '[name].[contenthash].js', }, }, }, { @@ -48,7 +50,7 @@ const cvatData = { loader: 'worker-loader', options: { publicPath: '/3rdparty/', - name: '3rdparty/[name].js', + name: '3rdparty/[name].[contenthash].js', }, }, }, diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 6960db8d..fbbd3cea 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.2.0", + "version": "1.8.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13,14 +13,19 @@ } }, "@ant-design/create-react-context": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@ant-design/create-react-context/-/create-react-context-0.2.4.tgz", - "integrity": "sha512-8sw+/w6r+aEbd+OJ62ojoSE4zDt/3yfQydmbWFznoftjr8v/opOswGjM+/MU0rSaREbluqzOmZ6xdecHpSaS2w==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@ant-design/create-react-context/-/create-react-context-0.2.5.tgz", + "integrity": "sha512-1rMAa4qgP2lfl/QBH9i78+Gjxtj9FTMpMyDGZsEBW5Kih72EuUo9958mV8PgpRkh4uwPSQ7vVZWXeyNZXVAFDg==", "requires": { "gud": "^1.0.0", "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": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-2.1.1.tgz", @@ -975,6 +980,11 @@ "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", "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": { "version": "1.0.0", "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==", "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": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -1056,6 +1071,14 @@ "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": { "version": "16.9.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.3.tgz", @@ -1506,9 +1529,9 @@ } }, "antd": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/antd/-/antd-3.25.2.tgz", - "integrity": "sha512-+qF1bgU7rUkPIkggIIV0fmm+9pPacl50BBd6NNUR2+kKJOFYjwrnP39ZqJRsYNy5bX9VgR454fz9KEuW7HPjog==", + "version": "3.26.17", + "resolved": "https://registry.npmjs.org/antd/-/antd-3.26.17.tgz", + "integrity": "sha512-P9uSK8SZ/1AvhQCC6aaLEkVrQhjbfZyUnqNV+lDnPqtudnZD2Ycy7Og+/EhuOBsQpYQvVT2aPLMgQWFv8tdJkA==", "requires": { "@ant-design/create-react-context": "^0.2.4", "@ant-design/icons": "~2.1.1", @@ -1521,18 +1544,19 @@ "css-animation": "^1.5.0", "dom-closest": "^0.2.0", "enquire.js": "^2.1.6", + "is-mobile": "^2.1.0", "lodash": "^4.17.13", "moment": "^2.24.0", "omit.js": "^1.0.2", "prop-types": "^15.7.2", "raf": "^3.4.1", "rc-animate": "^2.10.2", - "rc-calendar": "~9.15.5", + "rc-calendar": "~9.15.7", "rc-cascader": "~0.17.4", "rc-checkbox": "~2.1.6", "rc-collapse": "~1.11.3", - "rc-dialog": "~7.5.2", - "rc-drawer": "~3.0.0", + "rc-dialog": "~7.6.0", + "rc-drawer": "~3.1.1", "rc-dropdown": "~2.4.1", "rc-editor-mention": "^1.1.13", "rc-form": "^2.4.10", @@ -1540,7 +1564,7 @@ "rc-mentions": "~0.4.0", "rc-menu": "~7.5.1", "rc-notification": "~3.3.1", - "rc-pagination": "~1.20.5", + "rc-pagination": "~1.20.11", "rc-progress": "~2.5.0", "rc-rate": "~2.5.0", "rc-resize-observer": "^0.1.0", @@ -1548,15 +1572,15 @@ "rc-slider": "~8.7.1", "rc-steps": "~3.5.0", "rc-switch": "~1.9.0", - "rc-table": "~6.9.4", - "rc-tabs": "~9.6.4", + "rc-table": "~6.10.5", + "rc-tabs": "~9.7.0", "rc-time-picker": "~3.7.1", "rc-tooltip": "~3.7.3", "rc-tree": "~2.1.0", "rc-tree-select": "~2.9.1", "rc-trigger": "^2.6.2", "rc-upload": "~2.9.1", - "rc-util": "^4.10.0", + "rc-util": "^4.16.1", "react-lazy-load": "^3.0.13", "react-lifecycles-compat": "^3.0.4", "react-slick": "~0.25.2", @@ -3306,7 +3330,6 @@ "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", - "lodash": "^4.17.13", "mkdirp": "^0.5.1", "output-file-sync": "^2.0.0", "slash": "^2.0.0", @@ -3336,7 +3359,6 @@ "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", - "lodash": "^4.17.13", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" @@ -3372,7 +3394,6 @@ "requires": { "@babel/types": "^7.5.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0", "trim-right": "^1.0.1" } @@ -3423,8 +3444,7 @@ "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", "requires": { "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.5.5", - "lodash": "^4.17.13" + "@babel/types": "^7.5.5" } }, "@babel/helper-explode-assignable-expression": { @@ -3487,8 +3507,7 @@ "@babel/helper-simple-access": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", "@babel/template": "^7.4.4", - "@babel/types": "^7.5.5", - "lodash": "^4.17.13" + "@babel/types": "^7.5.5" } }, "@babel/helper-optimise-call-expression": { @@ -3507,10 +3526,7 @@ "@babel/helper-regex": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", - "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", - "requires": { - "lodash": "^4.17.13" - } + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==" }, "@babel/helper-remap-async-to-generator": { "version": "7.1.0", @@ -3622,7 +3638,6 @@ "requires": { "@babel/types": "^7.8.7", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, @@ -3734,8 +3749,7 @@ "@babel/parser": "^7.8.6", "@babel/types": "^7.8.6", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "globals": "^11.1.0" } }, "@babel/types": { @@ -3744,7 +3758,6 @@ "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.13", "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", "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-classes": { @@ -4232,8 +4244,7 @@ "@babel/parser": "^7.5.5", "@babel/types": "^7.5.5", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "globals": "^11.1.0" }, "dependencies": { "debug": { @@ -4257,7 +4268,6 @@ "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } }, @@ -5134,7 +5144,6 @@ "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", "inherits": "^2.0.1", "parse-asn1": "^5.0.0" } @@ -5677,8 +5686,7 @@ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "bn.js": "^4.1.0" } }, "create-error-class": { @@ -6167,20 +6175,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz", "integrity": "sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==" }, - "elliptic": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", - "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -6292,7 +6286,6 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", @@ -7215,7 +7208,6 @@ "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", "requires": { "glob": "~7.1.1", - "lodash": "~4.17.12", "minimatch": "~3.0.2" } }, @@ -7434,7 +7426,6 @@ "requires": { "http-proxy": "^1.17.0", "is-glob": "^4.0.0", - "lodash": "^4.17.11", "micromatch": "^3.1.10" } }, @@ -7593,7 +7584,6 @@ "cli-width": "^2.0.0", "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.12", "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.4.0", @@ -8098,11 +8088,6 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -8641,7 +8626,6 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash": "^4.17.15", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.13.2", @@ -10273,7 +10257,6 @@ "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", "requires": { "glob": "^7.0.0", - "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", "yargs": "^7.0.0" }, @@ -11221,7 +11204,6 @@ "integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==", "requires": { "ajv": "^6.10.2", - "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" }, @@ -12096,15 +12078,9 @@ "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", "requires": { "http-parser-js": ">=0.4.0 <0.4.11", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" + "safe-buffer": ">=5.1.0" } }, - "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -14113,7 +14089,6 @@ "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", "inherits": "^2.0.1", "parse-asn1": "^5.0.0" } @@ -14530,8 +14505,7 @@ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "bn.js": "^4.1.0" } }, "create-hash": { @@ -14996,20 +14970,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.377.tgz", "integrity": "sha512-cm2WzMKf/3dW5+hNANKm8GAW6SwIWOqLTJ6GPCD0Bbw1qJ9Wzm9nmx9M+byzSsgw8CdCv5fb/wzLFqVS5h6QrA==" }, - "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -18344,9 +18304,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash.sortby": { "version": "4.7.0", @@ -21348,9 +21308,9 @@ } }, "dom-align": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.10.2.tgz", - "integrity": "sha512-AYZUzLepy05E9bCY4ExoqHrrIlM49PEak9oF93JEFoibqKL0F7w5DLM70/rosLOawerWZ3MlepQcl+EmHskOyw==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.0.tgz", + "integrity": "sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA==" }, "dom-closest": { "version": "0.2.0", @@ -21526,9 +21486,9 @@ "dev": true }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -24417,6 +24377,11 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -24784,9 +24749,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash-es": { "version": "4.17.15", @@ -24920,6 +24885,11 @@ "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": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -25318,9 +25288,9 @@ "dev": true }, "mutationobserver-shim": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", - "integrity": "sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==" + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.7.tgz", + "integrity": "sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ==" }, "mute-stream": { "version": "0.0.8", @@ -26225,6 +26195,11 @@ "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": { "version": "1.0.25", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", @@ -27028,9 +27003,9 @@ } }, "rc-animate": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.2.tgz", - "integrity": "sha512-cE/A7piAzoWFSgUD69NmmMraqCeqVBa51UErod8NS3LUEqWfppSVagHfa0qHAlwPVPiIBg3emRONyny3eiH0Dg==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", @@ -27042,9 +27017,9 @@ } }, "rc-calendar": { - "version": "9.15.8", - "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.8.tgz", - "integrity": "sha512-x3zVaZSRX7FkRNKw7nz3tutwrlIrU1aqMn5GtRUmlf84GnXLtd9fuuydxeNkFWfcHry3BPSto7+r9TK2al0h+g==", + "version": "9.15.11", + "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.11.tgz", + "integrity": "sha512-qv0VXfAAnysMWJigxaP6se4bJHvr17D9qsLbi8BOpdgEocsS0RkgY1IUiFaOVYKJDy/EyLC447O02sV/y5YYBg==", "requires": { "babel-runtime": "6.x", "classnames": "2.x", @@ -27081,9 +27056,9 @@ } }, "rc-collapse": { - "version": "1.11.7", - "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-1.11.7.tgz", - "integrity": "sha512-ge3EEzIFtrDGuPX4bxXdQqwb91JnPIdj3B+FU88yNOUeOroNuA2q9kVK+UatpQ1Eft5hNo/ICTDrVFi8+685ng==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-1.11.8.tgz", + "integrity": "sha512-8EhfPyScTYljkbRuIoHniSwZagD5UPpZ3CToYgoNYWC85L2qCbPYF7+OaC713FOrIkp6NbfNqXsITNxmDAmxog==", "requires": { "classnames": "2.x", "css-animation": "1.x", @@ -27095,23 +27070,22 @@ } }, "rc-dialog": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.5.13.tgz", - "integrity": "sha512-tmubIipW/qoCmRlHHV8tpepDaFhuhk+SeSFSyRhNKW4mYgflsEYQmYWilyCJHy6UzKl84bSyFvJskhc1z1Hniw==", + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.6.1.tgz", + "integrity": "sha512-KUKf+2eZ4YL+lnXMG3hR4ZtIhC9glfH27NtTVz3gcoDIPAf3uUvaXVRNoDCiSi+OGKLyIb/b6EoidFh6nQC5Wg==", "requires": { "babel-runtime": "6.x", "rc-animate": "2.x", - "rc-util": "^4.8.1" + "rc-util": "^4.16.1" } }, "rc-drawer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-3.0.2.tgz", - "integrity": "sha512-oPScGXB/8/ov9gEFLxPH8RBv/9jLTZboZtyF/GgrrnCAvbFwUxXdELH6n6XIowmuDKKvTGIMgZdnao0T46Yv3A==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-3.1.3.tgz", + "integrity": "sha512-2z+RdxmzXyZde/1OhVMfDR1e/GBswFeWSZ7FS3Fdd0qhgVdpV1wSzILzzxRaT481ItB5hOV+e8pZT07vdJE8kg==", "requires": { - "babel-runtime": "^6.26.0", "classnames": "^2.2.6", - "rc-util": "^4.11.2", + "rc-util": "^4.16.1", "react-lifecycles-compat": "^3.0.4" } }, @@ -27157,9 +27131,9 @@ } }, "rc-form": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.10.tgz", - "integrity": "sha512-h6a5Nvn6fMe3BfLpIWwL2RUkfXs1tvtifblTgGgH0UfzGgiQ5M12jiMJaAXek7TDDBUw90/c5vlZ6wFZjW0IgQ==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.11.tgz", + "integrity": "sha512-8BL+FNlFLTOY/A5X6tU35GQJLSIpsmqpwn/tFAYQTczXc4dMJ33ggtH248Cum8+LS0jLTsJKG2L4Qp+1CkY+sA==", "requires": { "async-validator": "~1.11.3", "babel-runtime": "6.x", @@ -27172,9 +27146,9 @@ } }, "rc-hammerjs": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/rc-hammerjs/-/rc-hammerjs-0.6.9.tgz", - "integrity": "sha512-4llgWO3RgLyVbEqUdGsDfzUDqklRlQW5VEhE3x35IvhV+w//VPRG34SBavK3D2mD/UaLKaohgU41V4agiftC8g==", + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/rc-hammerjs/-/rc-hammerjs-0.6.10.tgz", + "integrity": "sha512-Vgh9qIudyN5CHRop4M+v+xUniQBFWXKrsJxQRVtJOi2xgRrCeI52/bkpaL5HWwUhqTK9Ayq0n7lYTItT6ld5rg==", "requires": { "babel-runtime": "6.x", "hammerjs": "^2.0.8", @@ -27182,9 +27156,9 @@ } }, "rc-input-number": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-4.5.1.tgz", - "integrity": "sha512-grO7/Lau7iv3NyHVyCajE1LuGLqGkG1tEAAZSwm9M0esYfrwXVSip4qhb5sF+8g6ACsiI20sOVLIihXuhSoifA==", + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-4.5.7.tgz", + "integrity": "sha512-99PrQ90sTOKyyj7eu0VzwxY17xQ+bwG1XTQd+bTwFQ+IOUkIw7L4qSAYxt58sVYL+Cw+bu/RAtT2IpT9yC2pCQ==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.0", @@ -27194,9 +27168,9 @@ } }, "rc-mentions": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-0.4.1.tgz", - "integrity": "sha512-XSJp6kcEPydUaM0I/gnxpXggiKgA5FjgFPKZCMQBDQJYUjXpQNyg5ogNkOJt1/4B2P7pwbYPZXgxP/30yZVahA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-0.4.2.tgz", + "integrity": "sha512-DTZurQzacLXOfVuiHydGzqkq7cFMHXF18l2jZ9PhWUn2cqvOSY3W4osN0Pq29AOMOBpcxdZCzgc7Lb0r/bgkDw==", "requires": { "@ant-design/create-react-context": "^0.2.4", "classnames": "^2.2.6", @@ -27207,9 +27181,9 @@ } }, "rc-menu": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.3.tgz", - "integrity": "sha512-H/jUyGbJxZI/iuVdC6Iu9KHfz7tucoqK0Vn8ahDnv+ppc1PnKb4SkBbXn5LrmUyaj7thCBiaktBxVnUXSmNE2g==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.5.tgz", + "integrity": "sha512-4YJXJgrpUGEA1rMftXN7bDhrV5rPB8oBJoHqT+GVXtIWCanfQxEnM3fmhHQhatL59JoAFMZhJaNzhJIk4FUWCQ==", "requires": { "classnames": "2.x", "dom-scroll-into-view": "1.x", @@ -27235,9 +27209,9 @@ } }, "rc-pagination": { - "version": "1.20.11", - "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.11.tgz", - "integrity": "sha512-2wKO5kO+ELx1/zlqTY8TwGBruzofi+1BcZ7Z4xalMlLbDMTuUU4FDljbBBP/n9D2llK+NtgWA619PMBhInozZw==", + "version": "1.20.15", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.15.tgz", + "integrity": "sha512-/Xr4/3GOa1DtL8iCYl7qRUroEMrRDhZiiuHwcVFfSiwa9LYloMlUWcOJsnr8LN6A7rLPdm3/CHStUNeYd+2pKw==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", @@ -27246,18 +27220,18 @@ } }, "rc-progress": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.5.2.tgz", - "integrity": "sha512-ajI+MJkbBz9zYDuE9GQsY5gsyqPF7HFioZEDZ9Fmc+ebNZoiSeSJsTJImPFCg0dW/5WiRGUy2F69SX1aPtSJgA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.5.3.tgz", + "integrity": "sha512-K2fa4CnqGehLZoMrdmBeZ86ONSTVcdk5FlqetbwJ3R/+42XfqhwQVOjWp2MH4P7XSQOMAGcNOy1SFfCP3415sg==", "requires": { "babel-runtime": "6.x", "prop-types": "^15.5.8" } }, "rc-rate": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.5.0.tgz", - "integrity": "sha512-aXX5klRqbVZxvLghcKnLqqo7LvLVCHswEDteWsm5Gb7NBIPa1YKTcAbvb5SZ4Z4i4EeRoZaPwygRAWsQgGtbKw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.5.1.tgz", + "integrity": "sha512-3iJkNJT8xlHklPCdeZtUZmJmRVUbr6AHRlfSsztfYTXVlHrv2TcPn3XkHsH+12j812WVB7gvilS2j3+ffjUHXg==", "requires": { "classnames": "^2.2.5", "prop-types": "^15.5.8", @@ -27276,9 +27250,9 @@ } }, "rc-select": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.1.tgz", - "integrity": "sha512-nW/Zr2OCgxN26OX8ff3xcO1wK0e1l5ixnEfyN15Rbdk7TNI/rIPJIjPCQAoihRpk9A2C/GH8pahjlvKV1Vj++g==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.3.tgz", + "integrity": "sha512-WhswxOMWiNnkXRbxyrj0kiIvyCfo/BaRPaYbsDetSIAU2yEDwKHF798blCP5u86KLOBKBvtxWLFCkSsQw1so5w==", "requires": { "babel-runtime": "^6.23.0", "classnames": "2.x", @@ -27331,9 +27305,9 @@ } }, "rc-table": { - "version": "6.9.5", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-6.9.5.tgz", - "integrity": "sha512-STL6387A/izVh6r9F1WDiIIZ0QeubCdTgIlzMeGTSl/bXhB0VqjAZEikvoijPoauTjJIkIzVuQEIDjOhAWbpkQ==", + "version": "6.10.15", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-6.10.15.tgz", + "integrity": "sha512-LAr0M/gqt+irOjvPNBLApmQ0CUHNOfKsEBhu1uIuB3OlN1ynA9z+sdoTQyNd9+8NSl0MYnQOOfhtLChAY7nU0A==", "requires": { "classnames": "^2.2.5", "component-classes": "^1.2.6", @@ -27346,9 +27320,9 @@ } }, "rc-tabs": { - "version": "9.6.7", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-9.6.7.tgz", - "integrity": "sha512-OXbDOgaqv2MGK9QaDi6cdva6bNz3XGw+M9BHQpm1gTGmVQEGx5VcclDClH/3xobIzooxy8hrxg/s0rTlgDnC2w==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-9.7.0.tgz", + "integrity": "sha512-kvmgp8/MfLzFZ06hWHignqomFQ5nF7BqKr5O1FfhE4VKsGrep52YSF/1MvS5oe0NPcI9XGNS2p751C5v6cYDpQ==", "requires": { "@ant-design/create-react-context": "^0.2.4", "babel-runtime": "6.x", @@ -27364,9 +27338,9 @@ } }, "rc-time-picker": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/rc-time-picker/-/rc-time-picker-3.7.2.tgz", - "integrity": "sha512-UVWO9HXGyZoM4I2THlJsEAFcZQz+tYwdcpoHXCEFZsRLz9L2+7vV4EMp9Wa3UrtzMFEt83qSAX/90dCJeKl9sg==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/rc-time-picker/-/rc-time-picker-3.7.3.tgz", + "integrity": "sha512-Lv1Mvzp9fRXhXEnRLO4nW6GLNxUkfAZ3RsiIBsWjGjXXvMNjdr4BX/ayElHAFK0DoJqOhm7c5tjmIYpEOwcUXg==", "requires": { "classnames": "2.x", "moment": "2.x", @@ -27387,9 +27361,9 @@ } }, "rc-tree": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.3.tgz", - "integrity": "sha512-COvV65spQ6omrHBUhHRKqKNL5+ddXjlS+qWZchaL9FFuQNvjM5pjp9RnmMWK4fJJ5kBhhpLneh6wh9Vh3kSMXQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.4.tgz", + "integrity": "sha512-Xey794Iavgs8YldFlXcZLOhfcIhlX5Oz/yfKufknBXf2AlZCOkc7aHqSM9uTF7fBPtTGPhPxNEfOqHfY7b7xng==", "requires": { "@ant-design/create-react-context": "^0.2.4", "classnames": "2.x", @@ -27401,51 +27375,27 @@ } }, "rc-tree-select": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-2.9.1.tgz", - "integrity": "sha512-AfJQC1ZzaeH+Onmx84TtVLUL2guBZe7exA8XSfj1RRB1doDbYGTtybzpP3CEw/tuSftSRnz+iPt+iaxRTrgXRw==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-2.9.4.tgz", + "integrity": "sha512-0HQkXAN4XbfBW20CZYh3G+V+VMrjX42XRtDCpyv6PDUm5vikC0Ob682ZBCVS97Ww2a5Hf6Ajmu0ahWEdIEpwhg==", "requires": { "classnames": "^2.2.1", "dom-scroll-into-view": "^1.2.1", "prop-types": "^15.5.8", "raf": "^3.4.0", "rc-animate": "^2.8.2", - "rc-tree": "~2.0.0", - "rc-trigger": "^3.0.0-rc.2", + "rc-tree": "~2.1.0", + "rc-trigger": "^3.0.0", "rc-util": "^4.5.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.0.2", "warning": "^4.0.1" }, "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": { - "version": "3.0.0-rc.3", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-3.0.0-rc.3.tgz", - "integrity": "sha512-4vB6cpxcUdm2qO5VtB9q1TZz0MoWm9BzFLvGknulphGrl1qI6uxUsPDCvqnmujdpDdAKGGfjxntFpA7RtAwkFQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-3.0.0.tgz", + "integrity": "sha512-hQxbbJpo23E2QnYczfq3Ec5J5tVl2mUDhkqxrEsQAqk16HfADQg+iKNWzEYXyERSncdxfnzYuaBgy764mNRzTA==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", @@ -27453,25 +27403,37 @@ "raf": "^3.4.0", "rc-align": "^2.4.1", "rc-animate": "^3.0.0-rc.1", - "rc-util": "^4.4.0" + "rc-util": "^4.15.7" }, "dependencies": { "rc-animate": { - "version": "3.0.0-rc.6", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-3.0.0-rc.6.tgz", - "integrity": "sha512-oBLPpiT6Q4t6YvD/pkLcmofBP1p01TX0Otse8Q4+Mxt8J+VSDflLZGIgf62EwkvRwsQUkLPjZVFBsldnPKLzjg==", - "requires": { - "babel-runtime": "6.x", - "classnames": "^2.2.5", - "component-classes": "^1.2.6", - "fbjs": "^0.8.16", - "prop-types": "15.x", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-3.1.0.tgz", + "integrity": "sha512-8FsM+3B1H+0AyTyGggY6JyVldHTs1CyYT8CfTmG/nGHHXlecvSLeICJhcKgRLjUiQlctNnRtB1rwz79cvBVmrw==", + "requires": { + "@ant-design/css-animation": "^1.7.2", + "classnames": "^2.2.6", "raf": "^3.4.0", - "rc-util": "^4.5.0", - "react-lifecycles-compat": "^3.0.4" + "rc-util": "^5.0.1" + }, + "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": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.9.2.tgz", - "integrity": "sha512-USjuWpTRJl3my32G5woysTaGrAld+S4dvvZ9kW6RX/RkekfmLDjvWc5ho8Mj/+6B6/tDRJnyGyvMxMQNkW7cvw==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.9.4.tgz", + "integrity": "sha512-WXt0HGxXyzLrPV6iec/96Rbl/6dyrAW8pKuY6wwD7yFYwfU5bjgKjv7vC8KNMJ6wzitFrZjnoiogNL3dF9dj3Q==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.5", @@ -27501,15 +27463,22 @@ } }, "rc-util": { - "version": "4.15.6", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.15.6.tgz", - "integrity": "sha512-W6HB1gIn+xZLxmQfLkhMnAtaZY9RktcOH2I0Tbam4D4ZDFrkO33f3M7IolN0EPtLMpf4Mv/dEQNclY77/PtBpg==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", "requires": { "add-dom-event-listener": "^1.1.0", - "babel-runtime": "6.x", "prop-types": "^15.5.10", + "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "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": { @@ -27522,6 +27491,19 @@ "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": { "version": "16.11.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz", @@ -27654,6 +27636,14 @@ "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -28648,9 +28638,9 @@ } }, "shallow-equal": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.0.tgz", - "integrity": "sha512-Z21pVxR4cXsfwpMKMhCEIO1PCi5sp7KEp+CmOpBQ+E8GpHwKOw2sEzk7sgblM3d/j4z4gakoWEoPcjK0VJQogA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" }, "shallowequal": { "version": "1.1.0", @@ -30490,9 +30480,9 @@ } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, "whatwg-fetch": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 00831d57..1065c3f8 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.2.0", + "version": "1.8.4", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { @@ -47,22 +47,26 @@ "worker-loader": "^2.0.0" }, "dependencies": { - "cvat-core": "file:../cvat-core", - "cvat-canvas": "file:../cvat-canvas", + "@types/platform": "^1.3.2", "@types/react": "^16.9.2", + "@types/react-color": "^3.0.2", "@types/react-dom": "^16.9.0", "@types/react-redux": "^7.1.2", "@types/react-router": "^5.0.5", "@types/react-router-dom": "^5.1.0", "@types/react-share": "^3.0.1", "@types/redux-logger": "^3.0.7", - "antd": "^3.25.2", + "antd": "^3.26.17", "copy-to-clipboard": "^3.2.0", + "cvat-canvas": "file:../cvat-canvas", + "cvat-core": "file:../cvat-core", "dotenv-webpack": "^1.7.0", "error-stack-parser": "^2.0.6", "moment": "^2.24.0", + "platform": "^1.3.6", "prop-types": "^15.7.2", "react": "^16.9.0", + "react-color": "^2.18.1", "react-dom": "^16.9.0", "react-hotkeys": "^2.0.0", "react-redux": "^7.1.1", diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index b88b9312..3bea5b6c 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -8,7 +8,7 @@ import { ActionCreator, Store, } from 'redux'; -import { ThunkAction } from 'redux-thunk'; +import { ThunkAction } from 'utils/redux'; import { CombinedState, @@ -148,8 +148,6 @@ export enum AnnotationActionTypes { GROUP_ANNOTATIONS_FAILED = 'GROUP_ANNOTATIONS_FAILED', SPLIT_ANNOTATIONS_SUCCESS = 'SPLIT_ANNOTATIONS_SUCCESS', 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', COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR', COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE', @@ -191,8 +189,7 @@ export enum AnnotationActionTypes { SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED', } -export function saveLogsAsync(): -ThunkAction, {}, {}, AnyAction> { +export function saveLogsAsync(): ThunkAction { return async (dispatch: ActionCreator) => { try { await logger.save(); @@ -236,7 +233,7 @@ export function switchZLayer(cur: number): AnyAction { }; } -export function fetchAnnotationsAsync(): ThunkAction, {}, {}, AnyAction> { +export function fetchAnnotationsAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { @@ -308,8 +305,7 @@ export function updateCanvasContextMenu( }; } -export function removeAnnotationsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { +export function removeAnnotationsAsync(sessionInstance: any): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { await sessionInstance.annotations.clear(); @@ -333,8 +329,7 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): -ThunkAction, {}, {}, AnyAction> { +export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state: CombinedState = getStore().getState(); @@ -404,8 +399,7 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function changeJobStatusAsync(jobInstance: any, status: string): -ThunkAction, {}, {}, AnyAction> { +export function changeJobStatusAsync(jobInstance: any, status: string): ThunkAction { return async (dispatch: ActionCreator): Promise => { const oldStatus = jobInstance.status; try { @@ -435,8 +429,7 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function collectStatisticsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { +export function collectStatisticsAsync(sessionInstance: any): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { dispatch({ @@ -477,7 +470,7 @@ export function propagateObjectAsync( objectState: any, from: number, to: number, -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const copy = { @@ -490,6 +483,7 @@ export function propagateObjectAsync( label: objectState.label, zOrder: objectState.zOrder, frame: from, + source: objectState.source, }; await sessionInstance.logger.log( @@ -542,7 +536,7 @@ export function changePropagateFrames(frames: number): AnyAction { } export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { 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): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const { instance: job } = state.annotation.job; @@ -751,7 +745,7 @@ ThunkAction, {}, {}, AnyAction> { } export function undoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state = getStore().getState(); @@ -794,7 +788,7 @@ ThunkAction, {}, {}, AnyAction> { } export function redoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state = getStore().getState(); @@ -906,25 +900,31 @@ export function confirmCanvasReady(): AnyAction { }; } +export function closeJob(): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + const { jobInstance } = receiveAnnotationsParameters(); + if (jobInstance) { + await jobInstance.task.close(); + } + + dispatch({ + type: AnnotationActionTypes.CLOSE_JOB, + }); + }; +} + export function getJobAsync( tid: number, jid: number, initialFrame: number, initialFilters: string[], -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const state: CombinedState = getStore().getState(); const filters = initialFilters; 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({ type: AnnotationActionTypes.GET_JOB, payload: { @@ -995,7 +995,7 @@ export function getJobAsync( } export function saveAnnotationsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1112,8 +1112,7 @@ export function splitTrack(enabled: boolean): AnyAction { }; } -export function updateAnnotationsAsync(statesToUpdate: any[]): -ThunkAction, {}, {}, AnyAction> { +export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { jobInstance, @@ -1158,7 +1157,7 @@ ThunkAction, {}, {}, AnyAction> { } export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1186,7 +1185,7 @@ ThunkAction, {}, {}, AnyAction> { } export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1224,7 +1223,7 @@ export function groupAnnotationsAsync( sessionInstance: any, frame: number, statesToGroup: any[], -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1260,7 +1259,7 @@ export function groupAnnotationsAsync( } export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): -ThunkAction, {}, {}, AnyAction> { +ThunkAction { return async (dispatch: ActionCreator): Promise => { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); try { @@ -1287,44 +1286,10 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function changeLabelColorAsync( - sessionInstance: any, - frameNumber: number, - label: any, - color: string, -): ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - 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( group: number, color: string, -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const groupStates = state.annotation.annotations.states @@ -1342,7 +1307,7 @@ export function searchAnnotationsAsync( sessionInstance: any, frameFrom: number, frameTo: number, -): ThunkAction, {}, {}, AnyAction> { +): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { const { filters } = receiveAnnotationsParameters(); @@ -1361,7 +1326,7 @@ export function searchAnnotationsAsync( }; } -export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> { +export function pasteShapeAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { canvas: { @@ -1420,7 +1385,7 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> }; } -export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAction> { +export function repeatDrawShapeAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { canvas: { @@ -1483,3 +1448,51 @@ export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAc } }; } + +export function redrawShapeAsync(): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + 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, + }); + } + } + }; +} diff --git a/cvat-ui/src/actions/auth-actions.ts b/cvat-ui/src/actions/auth-actions.ts index ed6ecd30..f12049fa 100644 --- a/cvat-ui/src/actions/auth-actions.ts +++ b/cvat-ui/src/actions/auth-actions.ts @@ -5,6 +5,7 @@ import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { UserConfirmation } from 'components/register-page/register-form'; import getCore from 'cvat-core-wrapper'; +import isReachable from 'utils/url-checker'; const cvat = getCore(); @@ -20,9 +21,16 @@ export enum AuthActionTypes { LOGOUT = 'LOGOUT', LOGOUT_SUCCESS = 'LOGOUT_SUCCESS', LOGOUT_FAILED = 'LOGOUT_FAILED', + CHANGE_PASSWORD = 'CHANGE_PASSWORD', + CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS', + CHANGE_PASSWORD_FAILED = 'CHANGE_PASSWORD_FAILED', + SWITCH_CHANGE_PASSWORD_DIALOG = 'SWITCH_CHANGE_PASSWORD_DIALOG', + LOAD_AUTH_ACTIONS = 'LOAD_AUTH_ACTIONS', + LOAD_AUTH_ACTIONS_SUCCESS = 'LOAD_AUTH_ACTIONS_SUCCESS', + LOAD_AUTH_ACTIONS_FAILED = 'LOAD_AUTH_ACTIONS_FAILED', } -const authActions = { +export const authActions = { authorizeSuccess: (user: any) => createAction(AuthActionTypes.AUTHORIZED_SUCCESS, { user }), authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }), login: () => createAction(AuthActionTypes.LOGIN), @@ -34,6 +42,21 @@ const authActions = { logout: () => createAction(AuthActionTypes.LOGOUT), logoutSuccess: () => createAction(AuthActionTypes.LOGOUT_SUCCESS), logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }), + changePassword: () => createAction(AuthActionTypes.CHANGE_PASSWORD), + changePasswordSuccess: () => createAction(AuthActionTypes.CHANGE_PASSWORD_SUCCESS), + changePasswordFailed: (error: any) => ( + createAction(AuthActionTypes.CHANGE_PASSWORD_FAILED, { error }) + ), + switchChangePasswordDialog: (showChangePasswordDialog: boolean) => ( + createAction(AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG, { showChangePasswordDialog }) + ), + loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS), + loadServerAuthActionsSuccess: (allowChangePassword: boolean) => ( + createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, { allowChangePassword }) + ), + loadServerAuthActionsFailed: (error: any) => ( + createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error }) + ), }; export type AuthActions = ActionUnion; @@ -52,10 +75,10 @@ export const registerAsync = ( dispatch(authActions.register()); try { - await cvat.server.register(username, firstName, lastName, email, password1, password2, confirmations); - const users = await cvat.users.get({ self: true }); + const user = await cvat.server.register(username, firstName, lastName, email, password1, password2, + confirmations); - dispatch(authActions.registerSuccess(users[0])); + dispatch(authActions.registerSuccess(user)); } catch (error) { dispatch(authActions.registerFailed(error)); } @@ -99,3 +122,30 @@ export const authorizedAsync = (): ThunkAction => async (dispatch) => { dispatch(authActions.authorizeFailed(error)); } }; + +export const changePasswordAsync = (oldPassword: string, + newPassword1: string, newPassword2: string): ThunkAction => async (dispatch) => { + dispatch(authActions.changePassword()); + + try { + await cvat.server.changePassword(oldPassword, newPassword1, newPassword2); + dispatch(authActions.changePasswordSuccess()); + } catch (error) { + dispatch(authActions.changePasswordFailed(error)); + } +}; + +export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => { + dispatch(authActions.loadServerAuthActions()); + + try { + const promises: Promise[] = [ + isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'), + ]; + const [allowChangePassword] = await Promise.all(promises); + + dispatch(authActions.loadServerAuthActionsSuccess(allowChangePassword)); + } catch (error) { + dispatch(authActions.loadServerAuthActionsFailed(error)); + } +}; diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index eebacbae..1f2afe14 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -3,27 +3,14 @@ // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import { - Model, - ModelType, - ModelFiles, - ActiveInference, - CombinedState, -} from 'reducers/interfaces'; +import { Model, ActiveInference, RQStatus } from 'reducers/interfaces'; import getCore from 'cvat-core-wrapper'; -export enum PreinstalledModels { - RCNN = 'RCNN Object Detector', - MaskRCNN = 'Mask RCNN Object Detector', -} - export enum ModelsActionTypes { GET_MODELS = 'GET_MODELS', GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS', GET_MODELS_FAILED = 'GET_MODELS_FAILED', DELETE_MODEL = 'DELETE_MODEL', - DELETE_MODEL_SUCCESS = 'DELETE_MODEL_SUCCESS', - DELETE_MODEL_FAILED = 'DELETE_MODEL_FAILED', CREATE_MODEL = 'CREATE_MODEL', CREATE_MODEL_SUCCESS = 'CREATE_MODEL_SUCCESS', CREATE_MODEL_FAILED = 'CREATE_MODEL_FAILED', @@ -50,28 +37,6 @@ export const modelsActions = { error, }, ), - deleteModelSuccess: (id: number) => createAction( - ModelsActionTypes.DELETE_MODEL_SUCCESS, { - id, - }, - ), - deleteModelFailed: (id: number, error: any) => createAction( - ModelsActionTypes.DELETE_MODEL_FAILED, { - error, id, - }, - ), - createModel: () => createAction(ModelsActionTypes.CREATE_MODEL), - createModelSuccess: () => createAction(ModelsActionTypes.CREATE_MODEL_SUCCESS), - createModelFailed: (error: any) => createAction( - ModelsActionTypes.CREATE_MODEL_FAILED, { - error, - }, - ), - createModelUpdateStatus: (status: string) => createAction( - ModelsActionTypes.CREATE_MODEL_STATUS_UPDATED, { - status, - }, - ), fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }), getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => createAction( ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, { @@ -96,7 +61,7 @@ export const modelsActions = { taskID, }, ), - cancelInferenceFaild: (taskID: number, error: any) => createAction( + cancelInferenceFailed: (taskID: number, error: any) => createAction( ModelsActionTypes.CANCEL_INFERENCE_FAILED, { taskID, error, @@ -113,361 +78,76 @@ export const modelsActions = { export type ModelsActions = ActionUnion; const core = getCore(); -const baseURL = core.config.backendAPI.slice(0, -7); export function getModelsAsync(): ThunkAction { - return async (dispatch, getState): Promise => { - const state: CombinedState = getState(); - const OpenVINO = state.plugins.list.AUTO_ANNOTATION; - const RCNN = state.plugins.list.TF_ANNOTATION; - const MaskRCNN = state.plugins.list.TF_SEGMENTATION; - + return async (dispatch): Promise => { dispatch(modelsActions.getModels()); - const models: Model[] = []; try { - if (OpenVINO) { - const response = await core.server.request( - `${baseURL}/auto_annotation/meta/get`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - data: JSON.stringify([]), - }, - ); - - - for (const model of response.models) { - models.push({ - id: model.id, - ownerID: model.owner, - primary: model.primary, - name: model.name, - uploadDate: model.uploadDate, - updateDate: model.updateDate, - labels: [...model.labels], - }); - } - } - - if (RCNN) { - models.push({ - id: null, - ownerID: null, - primary: true, - name: PreinstalledModels.RCNN, - uploadDate: '', - updateDate: '', - labels: ['surfboard', 'car', 'skateboard', 'boat', 'clock', - 'cat', 'cow', 'knife', 'apple', 'cup', 'tv', - 'baseball_bat', 'book', 'suitcase', 'tennis_racket', - 'stop_sign', 'couch', 'cell_phone', 'keyboard', - 'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant', - 'snowboard', 'bed', 'vase', 'teddy_bear', - 'toaster', 'wine_glass', 'traffic_light', - 'broccoli', 'backpack', 'carrot', 'potted_plant', - 'donut', 'umbrella', 'parking_meter', 'bottle', - 'sandwich', 'motorcycle', 'bear', 'banana', - 'person', 'scissors', 'elephant', 'dining_table', - 'toothbrush', 'toilet', 'skis', 'bowl', 'sheep', - 'refrigerator', 'oven', 'microwave', 'train', - 'orange', 'mouse', 'laptop', 'bench', 'bicycle', - 'fork', 'kite', 'zebra', 'baseball_glove', 'bus', - 'spoon', 'horse', 'handbag', 'pizza', 'sports_ball', - 'airplane', 'hair_drier', 'hot_dog', 'remote', - 'sink', 'dog', 'bird', 'giraffe', 'chair', - ], - }); - } - - if (MaskRCNN) { - models.push({ - id: null, - ownerID: null, - primary: true, - name: PreinstalledModels.MaskRCNN, - uploadDate: '', - updateDate: '', - labels: ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', - 'bus', 'train', 'truck', 'boat', 'traffic light', - 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', - 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', - 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', - 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', - 'kite', 'baseball bat', 'baseball glove', 'skateboard', - 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', - 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', - 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', - 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', - 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', - 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', - 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', - 'teddy bear', 'hair drier', 'toothbrush', - ], - }); - } + const models = (await core.lambda.list()) + .filter((model: Model) => ['detector', 'reid'].includes(model.type)); + dispatch(modelsActions.getModelsSuccess(models)); } catch (error) { dispatch(modelsActions.getModelsFailed(error)); - return; } - - dispatch(modelsActions.getModelsSuccess(models)); }; } -export function deleteModelAsync(id: number): ThunkAction { - return async (dispatch): Promise => { - 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 => { - async function checkCallback(id: string): Promise { - try { - const data = await core.server.request( - `${baseURL}/auto_annotation/check/${id}`, { - method: 'GET', - }, - ); - - switch (data.status) { - case 'failed': - dispatch(modelsActions.createModelFailed( - `Checking request has returned the "${data.status}" status. Message: ${data.error}`, - )); - break; - case 'unknown': - dispatch(modelsActions.createModelFailed( - `Checking request has returned the "${data.status}" status.`, - )); - break; - case 'finished': - dispatch(modelsActions.createModelSuccess()); - break; - default: - if ('progress' in data) { - modelsActions.createModelUpdateStatus(data.progress); - } - setTimeout(checkCallback.bind(null, id), 1000); - } - } catch (error) { - dispatch(modelsActions.createModelFailed(error)); - } - } - - dispatch(modelsActions.createModel()); - const data = new FormData(); - data.append('name', name); - data.append('storage', typeof files.bin === 'string' ? 'shared' : 'local'); - data.append('shared', global.toString()); - Object.keys(files).reduce((acc, key: string): FormData => { - acc.append(key, files[key]); - return acc; - }, data); - - try { - dispatch(modelsActions.createModelUpdateStatus('Request is beign sent..')); - const response = await core.server.request( - `${baseURL}/auto_annotation/create`, { - method: 'POST', - data, - contentType: false, - processData: false, - }, - ); - - dispatch(modelsActions.createModelUpdateStatus('Request is being processed..')); - setTimeout(checkCallback.bind(null, response.id), 1000); - } catch (error) { - dispatch(modelsActions.createModelFailed(error)); - } - }; -} interface InferenceMeta { - active: boolean; taskID: number; requestID: string; - modelType: ModelType; } -const timers: any = {}; - -async function timeoutCallback( - url: string, - taskID: number, - modelType: ModelType, +function listen( + inferenceMeta: InferenceMeta, dispatch: (action: ModelsActions) => void, -): Promise { - try { - delete timers[taskID]; - - const response = await core.server.request(url, { - method: 'GET', - }); - - const activeInference: ActiveInference = { - status: response.status, - progress: +response.progress || 0, - error: response.error || response.stderr || '', - modelType, - }; - - - if (activeInference.status === 'unknown') { - dispatch(modelsActions.getInferenceStatusFailed( - taskID, - new Error( - `Inference status for the task ${taskID} is unknown.`, - ), - )); - - return; - } - - if (activeInference.status === 'failed') { +): void { + const { taskID, requestID } = inferenceMeta; + core.lambda.listen(requestID, (status: RQStatus, progress: number, message: string) => { + if (status === RQStatus.failed || status === RQStatus.unknown) { dispatch(modelsActions.getInferenceStatusFailed( taskID, new Error( - `Inference status for the task ${taskID} is failed. ${activeInference.error}`, + `Inference status for the task ${taskID} is ${status}. ${message}`, ), )); return; } - if (activeInference.status !== 'finished') { - timers[taskID] = setTimeout( - timeoutCallback.bind( - null, - url, - taskID, - modelType, - dispatch, - ), 3000, - ); - } - - dispatch(modelsActions.getInferenceStatusSuccess(taskID, activeInference)); - } catch (error) { - dispatch(modelsActions.getInferenceStatusFailed(taskID, new Error( - `Server request for the task ${taskID} was failed`, - ))); - } -} - -function subscribe( - inferenceMeta: InferenceMeta, - dispatch: (action: ModelsActions) => void, -): void { - if (!(inferenceMeta.taskID in timers)) { - let requestURL = `${baseURL}`; - if (inferenceMeta.modelType === ModelType.OPENVINO) { - requestURL = `${requestURL}/auto_annotation/check`; - } else if (inferenceMeta.modelType === ModelType.RCNN) { - requestURL = `${requestURL}/tensorflow/annotation/check/task`; - } else if (inferenceMeta.modelType === ModelType.MASK_RCNN) { - requestURL = `${requestURL}/tensorflow/segmentation/check/task`; - } - requestURL = `${requestURL}/${inferenceMeta.requestID}`; - timers[inferenceMeta.taskID] = setTimeout( - timeoutCallback.bind( - null, - requestURL, - inferenceMeta.taskID, - inferenceMeta.modelType, - dispatch, - ), - ); - } + dispatch(modelsActions.getInferenceStatusSuccess(taskID, { + status, + progress, + error: message, + id: requestID, + })); + }).catch((error: Error) => { + dispatch(modelsActions.getInferenceStatusFailed(taskID, { + status: 'unknown', + progress: 0, + error: error.toString(), + id: requestID, + })); + }); } -export function getInferenceStatusAsync(tasks: number[]): ThunkAction { - return async (dispatch, getState): Promise => { - function parse(response: any, modelType: ModelType): InferenceMeta[] { - return Object.keys(response).map((key: string): InferenceMeta => ({ - taskID: +key, - requestID: response[key].rq_id || key, - active: typeof (response[key].active) === 'undefined' ? ['queued', 'started'] - .includes(response[key].status.toLowerCase()) : response[key].active, - modelType, - })); - } - - const state: CombinedState = getState(); - const OpenVINO = state.plugins.list.AUTO_ANNOTATION; - const RCNN = state.plugins.list.TF_ANNOTATION; - const MaskRCNN = state.plugins.list.TF_SEGMENTATION; - +export function getInferenceStatusAsync(): ThunkAction { + return async (dispatch): Promise => { const dispatchCallback = (action: ModelsActions): void => { dispatch(action); }; try { - if (OpenVINO) { - const response = await core.server.request( - `${baseURL}/auto_annotation/meta/get`, { - method: 'POST', - data: JSON.stringify(tasks), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - - parse(response.run, ModelType.OPENVINO) - .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) - .forEach((inferenceMeta: InferenceMeta): void => { - subscribe(inferenceMeta, dispatchCallback); - }); - } - - if (RCNN) { - const response = await core.server.request( - `${baseURL}/tensorflow/annotation/meta/get`, { - method: 'POST', - data: JSON.stringify(tasks), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - - parse(response, ModelType.RCNN) - .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) - .forEach((inferenceMeta: InferenceMeta): void => { - subscribe(inferenceMeta, dispatchCallback); - }); - } - - if (MaskRCNN) { - const response = await core.server.request( - `${baseURL}/tensorflow/segmentation/meta/get`, { - method: 'POST', - data: JSON.stringify(tasks), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - - parse(response, ModelType.MASK_RCNN) - .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) - .forEach((inferenceMeta: InferenceMeta): void => { - subscribe(inferenceMeta, dispatchCallback); - }); - } + const requests = await core.lambda.requests(); + requests + .map((request: any): object => ({ + taskID: +request.function.task, + requestID: request.id, + })) + .forEach((inferenceMeta: InferenceMeta): void => { + listen(inferenceMeta, dispatchCallback); + }); } catch (error) { dispatch(modelsActions.fetchMetaFailed(error)); } @@ -477,37 +157,20 @@ export function getInferenceStatusAsync(tasks: number[]): ThunkAction { export function startInferenceAsync( taskInstance: any, model: Model, - mapping: { - [index: string]: string; - }, - cleanOut: boolean, + body: object, ): ThunkAction { return async (dispatch): Promise => { try { - if (model.name === PreinstalledModels.RCNN) { - await core.server.request( - `${baseURL}/tensorflow/annotation/create/task/${taskInstance.id}`, - ); - } else if (model.name === PreinstalledModels.MaskRCNN) { - await core.server.request( - `${baseURL}/tensorflow/segmentation/create/task/${taskInstance.id}`, - ); - } else { - await core.server.request( - `${baseURL}/auto_annotation/start/${model.id}/${taskInstance.id}`, { - method: 'POST', - data: JSON.stringify({ - reset: cleanOut, - labels: mapping, - }), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - } + const requestID: string = await core.lambda.run(taskInstance, model, body); + + const dispatchCallback = (action: ModelsActions): void => { + dispatch(action); + }; - dispatch(getInferenceStatusAsync([taskInstance.id])); + listen({ + taskID: taskInstance.id, + requestID, + }, dispatchCallback); } catch (error) { dispatch(modelsActions.startInferenceFailed(taskInstance.id, error)); } @@ -518,30 +181,10 @@ export function cancelInferenceAsync(taskID: number): ThunkAction { return async (dispatch, getState): Promise => { try { const inference = getState().models.inferences[taskID]; - if (inference) { - if (inference.modelType === ModelType.OPENVINO) { - await core.server.request( - `${baseURL}/auto_annotation/cancel/${taskID}`, - ); - } else if (inference.modelType === ModelType.RCNN) { - await core.server.request( - `${baseURL}/tensorflow/annotation/cancel/task/${taskID}`, - ); - } else if (inference.modelType === ModelType.MASK_RCNN) { - await core.server.request( - `${baseURL}/tensorflow/segmentation/cancel/task/${taskID}`, - ); - } - - if (timers[taskID]) { - clearTimeout(timers[taskID]); - delete timers[taskID]; - } - } - + await core.lambda.cancel(inference.id); dispatch(modelsActions.cancelInferenceSuccess(taskID)); } catch (error) { - dispatch(modelsActions.cancelInferenceFaild(taskID, error)); + dispatch(modelsActions.cancelInferenceFailed(taskID, error)); } }; } diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts index 510f6590..6b5ca2c9 100644 --- a/cvat-ui/src/actions/plugins-actions.ts +++ b/cvat-ui/src/actions/plugins-actions.ts @@ -8,7 +8,7 @@ import PluginChecker from 'utils/plugin-checker'; export enum PluginsActionTypes { CHECK_PLUGINS = 'CHECK_PLUGINS', - CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS' + CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS', } type PluginObjects = Record; @@ -29,27 +29,20 @@ export function checkPluginsAsync(): ThunkAction { dispatch(pluginActions.checkPlugins()); const plugins: PluginObjects = { ANALYTICS: false, - AUTO_ANNOTATION: false, GIT_INTEGRATION: false, - TF_ANNOTATION: false, - TF_SEGMENTATION: false, - REID: false, DEXTR_SEGMENTATION: false, }; const promises: Promise[] = [ + // check must return true/false with no exceptions PluginChecker.check(SupportedPlugins.ANALYTICS), - PluginChecker.check(SupportedPlugins.AUTO_ANNOTATION), PluginChecker.check(SupportedPlugins.GIT_INTEGRATION), - PluginChecker.check(SupportedPlugins.TF_ANNOTATION), - PluginChecker.check(SupportedPlugins.TF_SEGMENTATION), PluginChecker.check(SupportedPlugins.DEXTR_SEGMENTATION), - PluginChecker.check(SupportedPlugins.REID), ]; const values = await Promise.all(promises); - [plugins.ANALYTICS, plugins.AUTO_ANNOTATION, plugins.GIT_INTEGRATION, plugins.TF_ANNOTATION, - plugins.TF_SEGMENTATION, plugins.DEXTR_SEGMENTATION, plugins.REID] = values; + [plugins.ANALYTICS, plugins.GIT_INTEGRATION, + plugins.DEXTR_SEGMENTATION] = values; dispatch(pluginActions.checkedAllPlugins(plugins)); }; } diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index d8443440..9108b02a 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -32,6 +32,8 @@ export enum SettingsActionTypes { SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING', SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', 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 { @@ -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, + }, + }; +} diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index fd1bfbc4..c61c12d7 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -102,13 +102,7 @@ ThunkAction, {}, {}, AnyAction> { const promises = array .map((task): string => (task as any).frames.preview()); - dispatch( - getInferenceStatusAsync( - array.map( - (task: any): number => task.id, - ), - ), - ); + dispatch(getInferenceStatusAsync()); for (const promise of promises) { try { @@ -350,10 +344,12 @@ function createTask(): AnyAction { return action; } -function createTaskSuccess(): AnyAction { +function createTaskSuccess(taskId: number): AnyAction { const action = { type: TasksActionTypes.CREATE_TASK_SUCCESS, - payload: {}, + payload: { + taskId, + }, }; return action; @@ -439,10 +435,10 @@ ThunkAction, {}, {}, AnyAction> { dispatch(createTask()); try { - await taskInstance.save((status: string): void => { + const savedTask = await taskInstance.save((status: string): void => { dispatch(createTaskUpdateStatus(status)); }); - dispatch(createTaskSuccess()); + dispatch(createTaskSuccess(savedTask.id)); } catch (error) { dispatch(createTaskFailed(error)); } diff --git a/cvat-ui/src/actions/useragreements-actions.ts b/cvat-ui/src/actions/useragreements-actions.ts index 29645ddb..95ec7003 100644 --- a/cvat-ui/src/actions/useragreements-actions.ts +++ b/cvat-ui/src/actions/useragreements-actions.ts @@ -4,7 +4,7 @@ import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import getCore from 'cvat-core-wrapper'; -import { UserAgreement } from 'reducers/interfaces' +import { UserAgreement } from 'reducers/interfaces'; const core = getCore(); @@ -16,10 +16,12 @@ export enum UserAgreementsActionTypes { const userAgreementsActions = { getUserAgreements: () => createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS), - getUserAgreementsSuccess: (userAgreements: UserAgreement[]) => - createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements), - getUserAgreementsFailed: (error: any) => - createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error }), + getUserAgreementsSuccess: (userAgreements: UserAgreement[]) => ( + createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements) + ), + getUserAgreementsFailed: (error: any) => ( + createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error }) + ), }; export type UserAgreementsActions = ActionUnion; diff --git a/cvat-ui/src/assets/colorize-icon.svg b/cvat-ui/src/assets/colorize-icon.svg new file mode 100644 index 00000000..300f5408 --- /dev/null +++ b/cvat-ui/src/assets/colorize-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/assets/reset-perspective.svg b/cvat-ui/src/assets/reset-perspective.svg new file mode 100644 index 00000000..2c7c0b83 --- /dev/null +++ b/cvat-ui/src/assets/reset-perspective.svg @@ -0,0 +1,4 @@ + + + + diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss index 6f545305..75b561ca 100644 --- a/cvat-ui/src/base.scss +++ b/cvat-ui/src/base.scss @@ -2,26 +2,27 @@ // // SPDX-License-Identifier: MIT -$header-color: #D8D8D8; +$header-color: #d8d8d8; $text-color: #303030; -$hover-menu-color: rgba(24,144,255,0.05); -$completed-progress-color: #61C200; -$inprogress-progress-color: #1890FF; -$pending-progress-color: #C1C1C1; +$hover-menu-color: rgba(24, 144, 255, 0.05); +$completed-progress-color: #61c200; +$inprogress-progress-color: #1890ff; +$pending-progress-color: #c1c1c1; $border-color-1: #c3c3c3; $border-color-2: #d9d9d9; $border-color-3: #242424; $border-color-hover: #40a9ff; $background-color-1: white; -$background-color-2: #F1F1F1; +$background-color-2: #f1f1f1; $transparent-color: rgba(0, 0, 0, 0); $player-slider-color: #979797; $player-buttons-color: #242424; -$danger-icon-color: #FF4136; -$info-icon-color: #0074D9; -$objects-bar-tabs-color: #BEBEBE; -$objects-bar-icons-color: #242424; // #6E6E6E -$active-object-item-background-color: #D8ECFF; -$slider-color: #1890FF; +$danger-icon-color: #ff4136; +$info-icon-color: #0074d9; +$objects-bar-tabs-color: #bebebe; +$objects-bar-icons-color: #242424; // #6e6e6e +$active-label-background-color: #d8ecff; +$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; diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx index 7d4f305b..d51560e2 100644 --- a/cvat-ui/src/components/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -15,16 +15,11 @@ interface Props { taskID: number; taskMode: string; bugTracker: string; - - loaders: string[]; - dumpers: string[]; + loaders: any[]; + dumpers: any[]; loadActivity: string | null; dumpActivities: string[] | null; exportActivities: string[] | null; - - installedTFAnnotation: boolean; - installedTFSegmentation: boolean; - installedAutoAnnotation: boolean; inferenceIsActive: boolean; onClickMenu: (params: ClickParam, file?: File) => void; @@ -44,12 +39,7 @@ export default function ActionsMenuComponent(props: Props): JSX.Element { taskID, taskMode, bugTracker, - - installedAutoAnnotation, - installedTFAnnotation, - installedTFSegmentation, inferenceIsActive, - dumpers, loaders, onClickMenu, @@ -58,9 +48,6 @@ export default function ActionsMenuComponent(props: Props): JSX.Element { loadActivity, } = props; - const renderModelRunner = installedAutoAnnotation - || installedTFAnnotation || installedTFSegmentation; - let latestParams: ClickParam | null = null; function onClickMenuWrapper(params: ClickParam | null, file?: File): void { const copyParams = params || latestParams; @@ -137,17 +124,12 @@ export default function ActionsMenuComponent(props: Props): JSX.Element { }) } {!!bugTracker && Open bug tracker} - { - renderModelRunner - && ( - - Automatic annotation - - ) - } + + Automatic annotation +
Delete diff --git a/cvat-ui/src/components/actions-menu/dump-submenu.tsx b/cvat-ui/src/components/actions-menu/dump-submenu.tsx index 23a6ab6c..f88cc62e 100644 --- a/cvat-ui/src/components/actions-menu/dump-submenu.tsx +++ b/cvat-ui/src/components/actions-menu/dump-submenu.tsx @@ -15,7 +15,7 @@ function isDefaultFormat(dumperName: string, taskMode: string): boolean { interface Props { taskMode: string; menuKey: string; - dumpers: string[]; + dumpers: any[]; dumpActivities: string[] | null; } @@ -30,21 +30,24 @@ export default function DumpSubmenu(props: Props): JSX.Element { return ( { - dumpers.map((dumper: string): JSX.Element => { - const pending = (dumpActivities || []).includes(dumper); - const isDefault = isDefaultFormat(dumper, taskMode); - return ( - - - {dumper} - {pending && } - - ); - }) + dumpers + .sort((a: any, b: any) => a.name.localeCompare(b.name)) + .map((dumper: any): JSX.Element => { + const pending = (dumpActivities || []).includes(dumper.name); + const disabled = !dumper.enabled || pending; + const isDefault = isDefaultFormat(dumper.name, taskMode); + return ( + + + {dumper.name} + {pending && } + + ); + }) } ); diff --git a/cvat-ui/src/components/actions-menu/export-submenu.tsx b/cvat-ui/src/components/actions-menu/export-submenu.tsx index e5dd53e8..8f6bb4f3 100644 --- a/cvat-ui/src/components/actions-menu/export-submenu.tsx +++ b/cvat-ui/src/components/actions-menu/export-submenu.tsx @@ -9,7 +9,7 @@ import Text from 'antd/lib/typography/Text'; interface Props { menuKey: string; - exporters: string[]; + exporters: any[]; exportActivities: string[] | null; } @@ -23,20 +23,23 @@ export default function ExportSubmenu(props: Props): JSX.Element { return ( { - exporters.map((exporter: string): JSX.Element => { - const pending = (exportActivities || []).includes(exporter); - return ( - - - {exporter} - {pending && } - - ); - }) + exporters + .sort((a: any, b: any) => a.name.localeCompare(b.name)) + .map((exporter: any): JSX.Element => { + const pending = (exportActivities || []).includes(exporter.name); + const disabled = !exporter.enabled || pending; + return ( + + + {exporter.name} + {pending && } + + ); + }) } ); diff --git a/cvat-ui/src/components/actions-menu/load-submenu.tsx b/cvat-ui/src/components/actions-menu/load-submenu.tsx index 110761a3..c167a01b 100644 --- a/cvat-ui/src/components/actions-menu/load-submenu.tsx +++ b/cvat-ui/src/components/actions-menu/load-submenu.tsx @@ -11,7 +11,7 @@ import Text from 'antd/lib/typography/Text'; interface Props { menuKey: string; - loaders: string[]; + loaders: any[]; loadActivity: string | null; onFileUpload(file: File): void; } @@ -27,34 +27,40 @@ export default function LoadSubmenu(props: Props): JSX.Element { return ( { - loaders.map((_loader: string): JSX.Element => { - const [loader, accept] = _loader.split('::'); - const pending = loadActivity === loader; - return ( - - { - onFileUpload(file); - return false; - }} + loaders + .sort((a: any, b: any) => a.name.localeCompare(b.name)) + .map((loader: any): JSX.Element => { + const accept = loader.format + .split(',') + .map((x: string) => `.${x.trimStart()}`) + .join(', '); // add '.' to each extension in a list + const pending = loadActivity === loader.name; + const disabled = !loader.enabled || !!loadActivity; + return ( + - - + { + onFileUpload(file); + return false; + }} + > + + - - ); - }) + + ); + }) } ); diff --git a/cvat-ui/src/components/actions-menu/styles.scss b/cvat-ui/src/components/actions-menu/styles.scss index 1c70d839..b2fc8ded 100644 --- a/cvat-ui/src/components/actions-menu/styles.scss +++ b/cvat-ui/src/components/actions-menu/styles.scss @@ -5,14 +5,14 @@ @import '../../base.scss'; .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 { background-color: $hover-menu-color; } .ant-menu-submenu-title { - margin: 0px; + margin: 0; width: 13em; } } @@ -30,10 +30,10 @@ } .ant-menu-item.cvat-menu-load-submenu-item { - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; - > span > .ant-upload { + > span > .ant-upload { width: 100%; height: 100%; diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index 5572ebdd..d98d4284 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -4,6 +4,7 @@ import './styles.scss'; import React, { useEffect } from 'react'; +import { useHistory } from 'react-router'; import Layout from 'antd/lib/layout'; import Spin from 'antd/lib/spin'; 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 StandardWorkspaceComponent from './standard-workspace/standard-workspace'; import AttributeAnnotationWorkspace from './attribute-annotation-workspace/attribute-annotation-workspace'; +import TagAnnotationWorkspace from './tag-annotation-workspace/tag-annotation-workspace'; interface Props { job: any | null | undefined; fetching: boolean; getJob(): void; saveLogs(): void; + closeJob(): void; workspace: Workspace; } @@ -27,10 +30,12 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { job, fetching, getJob, + closeJob, saveLogs, workspace, } = props; + const history = useHistory(); useEffect(() => { saveLogs(); const root = window.document.getElementById('root'); @@ -43,6 +48,10 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { if (root) { root.style.minHeight = ''; } + + if (!history.location.pathname.includes('/jobs')) { + closeJob(); + } }; }, []); @@ -70,15 +79,21 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { - { workspace === Workspace.STANDARD ? ( - + { workspace === Workspace.STANDARD && ( + - ) : ( - + )} + { workspace === Workspace.ATTRIBUTE_ANNOTATION && ( + )} + { workspace === Workspace.TAG_ANNOTATION && ( + + + + )} ); diff --git a/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx b/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx index f2ddbac9..f11893db 100644 --- a/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx +++ b/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx @@ -147,7 +147,7 @@ function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Ele placeholder={ underCursor ? ( <> - + { diff --git a/cvat-ui/src/components/annotation-page/appearance-block.tsx b/cvat-ui/src/components/annotation-page/appearance-block.tsx new file mode 100644 index 00000000..06a060fa --- /dev/null +++ b/cvat-ui/src/components/annotation-page/appearance-block.tsx @@ -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): 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 ( + + Appearance + } + key='appearance' + > +
+ Color by + + {ColorBy.LABEL} + {ColorBy.INSTANCE} + {ColorBy.GROUP} + + Opacity + + Selected opacity + + + Black borders + + + Show bitmap + + + Show projections + +
+
+
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(React.memo(AppearanceBlock)); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 1c24f961..71170227 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -5,22 +5,25 @@ import React, { useState, useEffect } from 'react'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys'; import { connect } from 'react-redux'; -import { Action } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; import Layout, { SiderProps } from 'antd/lib/layout'; import { SelectValue } from 'antd/lib/select'; -import { CheckboxChangeEvent } from 'antd/lib/checkbox'; import { Row, Col } from 'antd/lib/grid'; import Text from 'antd/lib/typography/Text'; import Icon from 'antd/lib/icon'; +import { ThunkDispatch } from 'utils/redux'; +import { Canvas } from 'cvat-canvas-wrapper'; import { LogType } from 'cvat-logger'; import { activateObject as activateObjectAction, updateAnnotationsAsync, + changeFrameAsync, } 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 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 AttributeSwitcher from './attribute-switcher'; import ObjectBasicsEditor from './object-basics-edtior'; @@ -35,11 +38,15 @@ interface StateToProps { jobInstance: any; keyMap: Record; normalizedKeyMap: Record; + canvasInstance: Canvas; + canvasIsReady: boolean; + curZLayer: number; } interface DispatchToProps { activateObject(clientID: number | null, attrID: number | null): void; updateAnnotations(statesToUpdate: any[]): void; + changeFrame(frame: number): void; } interface LabelAttrMap { @@ -53,11 +60,18 @@ function mapStateToProps(state: CombinedState): StateToProps { activatedStateID, activatedAttributeID, states, + zLayer: { + cur, + }, }, job: { instance: jobInstance, labels, }, + canvas: { + instance: canvasInstance, + ready: canvasIsReady, + }, }, shortcuts: { keyMap, @@ -73,10 +87,13 @@ function mapStateToProps(state: CombinedState): StateToProps { states, keyMap, normalizedKeyMap, + canvasInstance, + canvasIsReady, + curZLayer: cur, }; } -function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { +function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { return { activateObject(clientID: number, attrID: number): void { dispatch(activateObjectAction(clientID, attrID)); @@ -84,6 +101,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch): updateAnnotations(states): void { dispatch(updateAnnotationsAsync(states)); }, + changeFrame(frame: number): void { + dispatch(changeFrameAsync(frame)); + }, }; } @@ -95,11 +115,18 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. activatedAttributeID, jobInstance, updateAnnotations, + changeFrame, activateObject, keyMap, normalizedKeyMap, + canvasInstance, + canvasIsReady, + curZLayer, } = props; + const filteredStates = states.filter((state) => !state.outside + && !state.hidden + && state.zOrder <= curZLayer); const [labelAttrMap, setLabelAttrMap] = useState( labels.reduce((acc, label): LabelAttrMap => { 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 [activeObjectState] = activatedStateID === null - ? [null] : states.filter((objectState: any): boolean => ( - objectState.clientID === activatedStateID - )); + const collapse = (): void => { + const [collapser] = window.document + .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 ? labelAttrMap[activeObjectState.label.id] : null; - if (activeObjectState) { - const attribute = labelAttrMap[activeObjectState.label.id]; - if (attribute && attribute.id !== activatedAttributeID) { - activateObject(activatedStateID, attribute ? attribute.id : null); + if (canvasIsReady) { + if (activeObjectState) { + const attribute = labelAttrMap[activeObjectState.label.id]; + 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 => { - if (states.length) { - const index = states.indexOf(activeObjectState); + if (filteredStates.length) { + const index = filteredStates.indexOf(activeObjectState); let nextIndex = index + step; - if (nextIndex > states.length - 1) { + if (nextIndex > filteredStates.length - 1) { nextIndex = 0; } else if (nextIndex < 0) { - nextIndex = states.length - 1; + nextIndex = filteredStates.length - 1; } if (nextIndex !== index) { - const attribute = labelAttrMap[states[nextIndex].label.id]; - activateObject(states[nextIndex].clientID, attribute ? attribute.id : null); + const attribute = labelAttrMap[filteredStates[nextIndex].label.id]; + activateObject(filteredStates[nextIndex].clientID, attribute ? attribute.id : null); } } }; @@ -181,42 +224,74 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. collapsed: sidebarCollapsed, }; + const preventDefault = (event: KeyboardEvent | undefined): void => { + if (event) { + event.preventDefault(); + } + }; + const subKeyMap = { NEXT_ATTRIBUTE: keyMap.NEXT_ATTRIBUTE, PREVIOUS_ATTRIBUTE: keyMap.PREVIOUS_ATTRIBUTE, NEXT_OBJECT: keyMap.NEXT_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 = { NEXT_ATTRIBUTE: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); nextAttribute(1); }, PREVIOUS_ATTRIBUTE: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); nextAttribute(-1); }, NEXT_OBJECT: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); nextObject(1); }, PREVIOUS_OBJECT: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } - + preventDefault(event); 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) { @@ -227,7 +302,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. className={`cvat-objects-sidebar-sider ant-layout-sider-zero-width-trigger ant-layout-sider-zero-width-trigger-left`} - onClick={() => setSidebarCollapsed(!sidebarCollapsed)} + onClick={collapse} > {sidebarCollapsed ? : } @@ -242,15 +317,14 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. currentLabel={activeObjectState.label.name} clientID={activeObjectState.clientID} occluded={activeObjectState.occluded} - objectsCount={states.length} - currentIndex={states.indexOf(activeObjectState)} + objectsCount={filteredStates.length} + currentIndex={filteredStates.indexOf(activeObjectState)} normalizedKeyMap={normalizedKeyMap} nextObject={nextObject} /> { const labelName = value as string; const [newLabel] = labels @@ -258,10 +332,12 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. activeObjectState.label = newLabel; updateAnnotations([activeObjectState]); }} - setOccluded={(event: CheckboxChangeEvent): void => { - activeObjectState.occluded = event.target.checked; - updateAnnotations([activeObjectState]); - }} + /> + { activeAttribute @@ -276,6 +352,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. nextAttribute={nextAttribute} /> { @@ -300,12 +377,29 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. ) } + + { !sidebarCollapsed && } ); } return ( + {/* eslint-disable-next-line */} + + {sidebarCollapsed ? + : } + + + + + +
No objects found
diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index 6917e42f..2f0b4c00 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -13,6 +13,7 @@ import Input from 'antd/lib/input'; import consts from 'consts'; interface InputElementParameters { + clientID: number; attrID: number; inputType: string; values: string[]; @@ -24,6 +25,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { const { inputType, attrID, + clientID, values, currentValue, onChange, @@ -103,7 +105,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
) => { const { value } = event.target; @@ -257,13 +259,19 @@ function renderList(parameters: ListParameters): JSX.Element | null { } interface Props { + clientID: number; attribute: any; currentValue: string; onChange(value: string): void; } function AttributeEditor(props: Props): JSX.Element { - const { attribute, currentValue, onChange } = props; + const { + attribute, + currentValue, + onChange, + clientID, + } = props; const { inputType, values, id: attrID } = attribute; return ( @@ -271,6 +279,7 @@ function AttributeEditor(props: Props): JSX.Element { {renderList({ values, inputType, onChange })}
{renderInputElement({ + clientID, attrID, inputType, currentValue, diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx index da835b60..66357b2f 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx @@ -27,17 +27,17 @@ function AttributeSwitcher(props: Props): JSX.Element { const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`; return ( -
- +
+ - + {currentAttribute} {` [${currentIndex + 1}/${attributesCount}]`} - + diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx index 17a689a9..ea9db335 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx @@ -4,24 +4,15 @@ import React from 'react'; import Select, { SelectValue } from 'antd/lib/select'; -import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; interface Props { currentLabel: string; labels: any[]; - occluded: boolean; - setOccluded(event: CheckboxChangeEvent): void; changeLabel(value: SelectValue): void; } function ObjectBasicsEditor(props: Props): JSX.Element { - const { - currentLabel, - occluded, - labels, - setOccluded, - changeLabel, - } = props; + const { currentLabel, labels, changeLabel } = props; return (
@@ -35,7 +26,6 @@ function ObjectBasicsEditor(props: Props): JSX.Element { ))} - Occluded
); } diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx index 97e77d0b..5d69834d 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx @@ -31,18 +31,18 @@ function ObjectSwitcher(props: Props): JSX.Element { const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`; return ( -
- +
+ - + {currentLabel} {` ${clientID} `} {`[${currentIndex + 1}/${objectsCount}]`} - + diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss index 231cc13b..fbbd2e07 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss @@ -13,7 +13,8 @@ padding: 5px; } -.attribute-annotation-sidebar-switcher { +.attribute-annotation-sidebar-object-switcher, +.attribute-annotation-sidebar-attribute-switcher { display: flex; align-items: center; justify-content: space-between; @@ -36,7 +37,7 @@ align-items: center; justify-content: space-between; font-size: 18px; - margin: 10px 0px; + margin: 10px 0; } .attribute-annotations-sidebar-not-found-wrapper { @@ -45,7 +46,7 @@ } .attribute-annotation-sidebar-attr-list-wrapper { - margin: 10px 0px 10px 10px; + margin: 10px 0 10px 10px; } .attribute-annotation-sidebar-attr-elem-wrapper { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx index 3cbd316d..9293318f 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -28,7 +28,7 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null { return ReactDOM.createPortal(
- +
, window.document.body, ); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx index bd534747..9d6f56c0 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -2,40 +2,131 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import Button from 'antd/lib/button'; import Tooltip from 'antd/lib/tooltip'; +import { connect } from 'react-redux'; -interface Props { - activatedStateID: number | null; +import { CombinedState, ContextMenuType } from 'reducers/interfaces'; +import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions'; + +interface StateToProps { + activatedState: any | null; + selectedPoint: number | null; visible: boolean; - left: number; top: number; - onPointDelete(): void; + left: number; + type: ContextMenuType; } -export default function CanvasPointContextMenu(props: Props): JSX.Element | null { +function mapStateToProps(state: CombinedState): StateToProps { const { - onPointDelete, - activatedStateID, + annotation: { + 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, left, 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; - if (!visible || activatedStateID === null) { - return null; + const [contextMenuFor, setContextMenuFor] = useState(activatedState); + + if (activatedState !== contextMenuFor) { + setContextMenuFor(activatedState); + if (visible && type === ContextMenuType.CANVAS_SHAPE_POINT) { + onCloseContextMenu(); + } } - return ReactDOM.createPortal( -
- - - -
, - window.document.body, - ); + const onPointDelete = (): void => { + const { selectedPoint } = props; + if (contextMenuFor && selectedPoint !== null) { + contextMenuFor.points = contextMenuFor.points.slice(0, selectedPoint * 2) + .concat(contextMenuFor.points.slice(selectedPoint * 2 + 2)); + onUpdateAnnotations([contextMenuFor]); + onCloseContextMenu(); + } + }; + + 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( +
+ + + + {contextMenuFor && contextMenuFor.shapeType === 'polygon' && ( + + )} +
, + window.document.body, + )) : null; } + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(CanvasPointContextMenu); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index da293251..14a13014 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -58,13 +58,13 @@ interface Props { contrastLevel: number; saturationLevel: number; resetZoom: boolean; - contextVisible: boolean; - contextType: ContextMenuType; aamZoomMargin: number; showObjectsTextAlways: boolean; + showAllInterpolationTracks: boolean; workspace: Workspace; automaticBordering: boolean; keyMap: Record; + canvasBackgroundColor: string; switchableAutomaticBordering: boolean; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; @@ -93,6 +93,7 @@ interface Props { onChangeGridColor(color: GridColor): void; onSwitchGrid(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void; + onFetchAnnotation(): void; } export default class CanvasWrapperComponent extends React.PureComponent { @@ -101,7 +102,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { automaticBordering, showObjectsTextAlways, canvasInstance, - curZLayer, } = this.props; // It's awful approach from the point of view React @@ -115,7 +115,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, displayAllText: showObjectsTextAlways, }); - canvasInstance.setZLayer(curZLayer); this.initialSetup(); this.updateCanvas(); @@ -137,6 +136,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { curZLayer, resetZoom, grid, + gridSize, gridOpacity, gridColor, brightnessLevel, @@ -145,8 +145,11 @@ export default class CanvasWrapperComponent extends React.PureComponent { workspace, frameFetching, showObjectsTextAlways, + showAllInterpolationTracks, automaticBordering, showProjections, + canvasBackgroundColor, + onFetchAnnotation, } = this.props; if (prevProps.showObjectsTextAlways !== showObjectsTextAlways @@ -161,6 +164,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { }); } + if (prevProps.showAllInterpolationTracks !== showAllInterpolationTracks) { + onFetchAnnotation(); + } + if (prevProps.sidebarCollapsed !== sidebarCollapsed) { const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar'); if (sidebar) { @@ -179,6 +186,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } + if (gridSize !== prevProps.gridSize) { + canvasInstance.grid(gridSize, gridSize); + } + if (gridOpacity !== prevProps.gridOpacity || gridColor !== prevProps.gridColor || grid !== prevProps.grid) { @@ -204,17 +215,15 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - if (prevProps.curZLayer !== curZLayer) { - canvasInstance.setZLayer(curZLayer); - } - - if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) { + if (prevProps.annotations !== annotations + || prevProps.frameData !== frameData + || prevProps.curZLayer !== curZLayer) { this.updateCanvas(); } if (prevProps.frame !== frameData.number - && resetZoom - && workspace !== Workspace.ATTRIBUTE_ANNOTATION + && ((resetZoom && workspace !== Workspace.ATTRIBUTE_ANNOTATION) + || workspace === Workspace.TAG_ANNOTATION) ) { canvasInstance.html().addEventListener('canvas.setup', () => { canvasInstance.fit(); @@ -222,7 +231,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { } if (prevProps.opacity !== opacity || prevProps.blackBorders !== blackBorders - || prevProps.selectedOpacity !== selectedOpacity || prevProps.colorBy !== colorBy) { + || prevProps.selectedOpacity !== selectedOpacity || prevProps.colorBy !== colorBy + ) { this.updateShapesView(); } @@ -243,6 +253,13 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } + 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(); } @@ -382,10 +399,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { activatedStateID, onUpdateContextMenu, - contextType, } = 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, ContextMenuType.CANVAS_SHAPE); } @@ -442,7 +458,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { onActivateObject, } = this.props; - if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { + if (workspace !== Workspace.STANDARD) { return; } @@ -571,7 +587,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.fit(); } } - if (activatedState.objectType !== ObjectType.TAG) { + if (activatedState && activatedState.objectType !== ObjectType.TAG) { canvasInstance.activate(activatedStateID, activatedAttributeID); } const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); @@ -616,16 +632,15 @@ export default class CanvasWrapperComponent extends React.PureComponent { private updateCanvas(): void { const { + curZLayer, annotations, frameData, - frameAngle, canvasInstance, } = this.props; if (frameData !== null) { canvasInstance.setup(frameData, annotations - .filter((e) => e.objectType !== ObjectType.TAG)); - canvasInstance.rotate(frameAngle); + .filter((e) => e.objectType !== ObjectType.TAG), curZLayer); } } @@ -639,6 +654,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { brightnessLevel, contrastLevel, saturationLevel, + canvasBackgroundColor, } = this.props; // Size @@ -665,6 +681,11 @@ export default class CanvasWrapperComponent extends React.PureComponent { + `saturate(${saturationLevel / 100})`; } + const canvasWrapperElement = window.document.getElementsByClassName('cvat-canvas-container').item(0) as HTMLElement | null; + if (canvasWrapperElement) { + canvasWrapperElement.style.backgroundColor = canvasBackgroundColor; + } + // Events canvasInstance.html().addEventListener('canvas.setup', () => { const { activatedStateID, activatedAttributeID } = this.props; @@ -855,7 +876,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { defaultValue={0} onChange={(value: SliderValue): void => onSwitchZLayer(value as number)} /> - +
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 0d82144d..f6ffeb2a 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -37,13 +37,15 @@ interface Props { repeatDrawShape(): void; pasteShape(): void; resetGroup(): void; + redrawShape(): void; } export default function ControlsSideBarComponent(props: Props): JSX.Element { const { canvasInstance, activeControl, - + normalizedKeyMap, + keyMap, mergeObjects, groupObjects, splitTrack, @@ -51,8 +53,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { repeatDrawShape, pasteShape, resetGroup, - normalizedKeyMap, - keyMap, + redrawShape, } = props; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -65,6 +66,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { PASTE_SHAPE: keyMap.PASTE_SHAPE, SWITCH_DRAW_MODE: keyMap.SWITCH_DRAW_MODE, SWITCH_MERGE_MODE: keyMap.SWITCH_MERGE_MODE, + SWITCH_SPLIT_MODE: keyMap.SWITCH_SPLIT_MODE, SWITCH_GROUP_MODE: keyMap.SWITCH_GROUP_MODE, RESET_GROUP: keyMap.RESET_GROUP, CANCEL: keyMap.CANCEL, @@ -88,7 +90,12 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { canvasInstance.cancel(); // repeateDrawShapes gets all the latest parameters // and calls canvasInstance.draw() with them - repeatDrawShape(); + + if (event && event.shiftKey) { + redrawShape(); + } else { + repeatDrawShape(); + } } else { canvasInstance.draw({ enabled: false }); } @@ -102,6 +109,15 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { canvasInstance.merge({ enabled: !merging }); mergeObjects(!merging); }, + SWITCH_SPLIT_MODE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const splitting = activeControl === ActiveControl.SPLIT; + if (!splitting) { + canvasInstance.cancel(); + } + canvasInstance.split({ enabled: !splitting }); + splitTrack(!splitting); + }, SWITCH_GROUP_MODE: (event: KeyboardEvent | undefined) => { preventDefault(event); const grouping = activeControl === ActiveControl.GROUP; @@ -205,6 +221,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { /> diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx index 0222008d..e4f0ec15 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx @@ -24,7 +24,7 @@ function CursorControl(props: Props): JSX.Element { } = props; return ( - + + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx index 2397e346..e034956a 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx @@ -43,6 +43,7 @@ function DrawPointsControl(props: Props): JSX.Element { )} > diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx index 1be11c2f..3b80a54c 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx @@ -43,6 +43,7 @@ function DrawPolygonControl(props: Props): JSX.Element { )} > diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx index 854e2850..f09d26e6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx @@ -43,6 +43,7 @@ function DrawPolylineControl(props: Props): JSX.Element { )} > diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx index 3989521c..60e852b6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx @@ -45,6 +45,7 @@ function DrawRectangleControl(props: Props): JSX.Element { )} > diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index 1c61ed9f..5edfc758 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { Row, Col } from 'antd/lib/grid'; -import Select from 'antd/lib/select'; +import Select, { OptionProps } from 'antd/lib/select'; import Button from 'antd/lib/button'; import InputNumber from 'antd/lib/input-number'; import Radio, { RadioChangeEvent } from 'antd/lib/radio'; @@ -51,10 +51,6 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { onChangeCuboidDrawingMethod, } = props; - const trackDisabled = shapeType === ShapeType.POLYGON - || shapeType === ShapeType.POLYLINE - || (shapeType === ShapeType.POINTS && numberOfPoints !== 1); - return (
@@ -70,6 +66,15 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { { + changeAttribute(attrID, value); + }} + value={attrValue} + className='cvat-object-item-select-attribute' + > + { attrValues.map((value: string): JSX.Element => ( + + {value === consts.UNDEFINED_ATTRIBUTE_VALUE + ? consts.NO_BREAK_SPACE : value} + + )) } + + + + ); + } + + if (attrInputType === 'number') { + const [min, max, step] = attrValues.map((value: string): number => +value); + return ( + <> + + + {attrName} + + + + { + if (typeof (value) === 'number') { + changeAttribute( + attrID, `${clamp(value, min, max)}`, + ); + } + }} + value={+attrValue} + className='cvat-object-item-number-attribute' + min={min} + max={max} + step={step} + /> + + + ); + } + + const ref = useRef(null); + const [selection, setSelection] = useState<{ + start: number | null; + end: number | null; + direction: 'forward' | 'backward' | 'none' | null; + }>({ + start: null, + end: null, + direction: null, + }); + + useEffect(() => { + if (ref.current && ref.current.input) { + ref.current.input.selectionStart = selection.start; + ref.current.input.selectionEnd = selection.end; + ref.current.input.selectionDirection = selection.direction; + } + }, [attrValue]); + + return ( + <> + + + {attrName} + + + + ): void => { + if (ref.current && ref.current.input) { + setSelection({ + start: ref.current.input.selectionStart, + end: ref.current.input.selectionEnd, + direction: ref.current.input.selectionDirection, + }); + } + + changeAttribute(attrID, event.target.value); + }} + value={attrValue} + className='cvat-object-item-text-attribute' + /> + + + ); +} + +export default React.memo(ItemAttributeComponent, attrIsTheSame); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx new file mode 100644 index 00000000..bbfc0e03 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-basics.tsx @@ -0,0 +1,162 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useState } from 'react'; +import { Row, Col } from 'antd/lib/grid'; +import Icon from 'antd/lib/icon'; +import Select, { OptionProps } from 'antd/lib/select'; +import Dropdown from 'antd/lib/dropdown'; +import Text from 'antd/lib/typography/Text'; +import Tooltip from 'antd/lib/tooltip'; + +import { ObjectType, ShapeType, ColorBy } from 'reducers/interfaces'; +import ItemMenu from './object-item-menu'; + +interface Props { + clientID: number; + serverID: number | undefined; + labelID: number; + labels: any[]; + shapeType: ShapeType; + objectType: ObjectType; + color: string; + colorBy: ColorBy; + type: string; + locked: boolean; + changeColorShortcut: string; + copyShortcut: string; + pasteShortcut: string; + propagateShortcut: string; + toBackgroundShortcut: string; + toForegroundShortcut: string; + removeShortcut: string; + changeColor(color: string): void; + changeLabel(labelID: string): void; + copy(): void; + remove(): void; + propagate(): void; + createURL(): void; + switchOrientation(): void; + toBackground(): void; + toForeground(): void; + resetCuboidPerspective(): void; +} + +function ItemTopComponent(props: Props): JSX.Element { + const { + clientID, + serverID, + labelID, + labels, + shapeType, + objectType, + color, + colorBy, + type, + locked, + changeColorShortcut, + copyShortcut, + pasteShortcut, + propagateShortcut, + toBackgroundShortcut, + toForegroundShortcut, + removeShortcut, + changeColor, + changeLabel, + copy, + remove, + propagate, + createURL, + switchOrientation, + toBackground, + toForeground, + resetCuboidPerspective, + } = props; + + const [menuVisible, setMenuVisible] = useState(false); + const [colorPickerVisible, setColorPickerVisible] = useState(false); + + const changeMenuVisible = (visible: boolean): void => { + if (!visible && colorPickerVisible) return; + setMenuVisible(visible); + }; + + const changeColorPickerVisible = (visible: boolean): void => { + if (!visible) { + setMenuVisible(false); + } + setColorPickerVisible(visible); + }; + + return ( + + + {clientID} +
+ {type} + + + + + + + + + + + +
+ ); +} + +export default React.memo(ItemTopComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons.tsx new file mode 100644 index 00000000..f5c12697 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons.tsx @@ -0,0 +1,262 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row, Col } from 'antd/lib/grid'; +import Icon from 'antd/lib/icon'; +import Tooltip from 'antd/lib/tooltip'; + +import { + ObjectOutsideIcon, + FirstIcon, + LastIcon, + PreviousIcon, + NextIcon, +} from 'icons'; +import { ObjectType, ShapeType } from 'reducers/interfaces'; + +interface Props { + objectType: ObjectType; + shapeType: ShapeType; + occluded: boolean; + outside: boolean | undefined; + locked: boolean; + pinned: boolean; + hidden: boolean; + keyframe: boolean | undefined; + outsideDisabled: boolean; + hiddenDisabled: boolean; + keyframeDisabled: boolean; + switchOccludedShortcut: string; + switchOutsideShortcut: string; + switchLockShortcut: string; + switchHiddenShortcut: string; + switchKeyFrameShortcut: string; + nextKeyFrameShortcut: string; + prevKeyFrameShortcut: string; + + navigateFirstKeyframe: null | (() => void); + navigatePrevKeyframe: null | (() => void); + navigateNextKeyframe: null | (() => void); + navigateLastKeyframe: null | (() => void); + + setOccluded(): void; + unsetOccluded(): void; + setOutside(): void; + unsetOutside(): void; + setKeyframe(): void; + unsetKeyframe(): void; + lock(): void; + unlock(): void; + pin(): void; + unpin(): void; + hide(): void; + show(): void; +} + +function ItemButtonsComponent(props: Props): JSX.Element { + const { + objectType, + shapeType, + occluded, + outside, + locked, + pinned, + hidden, + keyframe, + outsideDisabled, + hiddenDisabled, + keyframeDisabled, + switchOccludedShortcut, + switchOutsideShortcut, + switchLockShortcut, + switchHiddenShortcut, + switchKeyFrameShortcut, + nextKeyFrameShortcut, + prevKeyFrameShortcut, + + navigateFirstKeyframe, + navigatePrevKeyframe, + navigateNextKeyframe, + navigateLastKeyframe, + + setOccluded, + unsetOccluded, + setOutside, + unsetOutside, + setKeyframe, + unsetKeyframe, + lock, + unlock, + pin, + unpin, + hide, + show, + } = props; + + const outsideStyle = outsideDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {}; + const hiddenStyle = hiddenDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {}; + const keyframeStyle = keyframeDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {}; + + + if (objectType === ObjectType.TRACK) { + return ( + + + + + { navigateFirstKeyframe + ? + : } + + + { navigatePrevKeyframe + ? ( + + + + ) + : } + + + { navigateNextKeyframe + ? ( + + + + ) + : } + + + { navigateLastKeyframe + ? + : } + + + + + + { outside + ? ( + + ) + : } + + + + + { locked + ? + : } + + + + + { occluded + ? + : } + + + + + + + + { keyframe + ? + : } + + + { + shapeType !== ShapeType.POINTS && ( + + + { pinned + ? + : } + + + ) + } + + + + ); + } + + if (objectType === ObjectType.TAG) { + return ( + + + + + + { locked + ? + : } + + + + + + ); + } + + return ( + + + + + + { locked + ? + : } + + + + + { occluded + ? + : } + + + + + + { + shapeType !== ShapeType.POINTS && ( + + + { pinned + ? + : } + + + ) + } + + + + ); +} + +export default React.memo(ItemButtonsComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-details.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-details.tsx new file mode 100644 index 00000000..f2e17d3b --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-details.tsx @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row } from 'antd/lib/grid'; +import Collapse from 'antd/lib/collapse'; + +import ItemAttribute from './object-item-attribute'; + +interface Props { + collapsed: boolean; + attributes: any[]; + values: Record; + changeAttribute(attrID: number, value: string): void; + collapse(): void; +} + +export function attrValuesAreEqual( + next: Record, prev: Record, +): boolean { + const prevKeys = Object.keys(prev); + const nextKeys = Object.keys(next); + + return nextKeys.length === prevKeys.length + && nextKeys.map((key: string): boolean => prev[+key] === next[+key]) + .every((value: boolean) => value); +} + +function attrAreTheSame( + prevProps: Props, + nextProps: Props, +): boolean { + return nextProps.collapsed === prevProps.collapsed + && nextProps.attributes === prevProps.attributes + && attrValuesAreEqual(nextProps.values, prevProps.values); +} + +function ItemAttributesComponent(props: Props): JSX.Element { + const { + collapsed, + attributes, + values, + changeAttribute, + collapse, + } = props; + + const sorted = [...attributes] + .sort((a: any, b: any): number => a.inputType.localeCompare(b.inputType)); + + return ( + + + Details} + key='details' + > + { sorted.map((attribute: any): JSX.Element => ( + + + + ))} + + + + ); +} + +export default React.memo(ItemAttributesComponent, attrAreTheSame); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx new file mode 100644 index 00000000..99db3b84 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item-menu.tsx @@ -0,0 +1,175 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Icon from 'antd/lib/icon'; +import Menu from 'antd/lib/menu'; +import Button from 'antd/lib/button'; +import Modal from 'antd/lib/modal'; +import Tooltip from 'antd/lib/tooltip'; + +import { + BackgroundIcon, + ForegroundIcon, + ResetPerspectiveIcon, + ColorizeIcon, +} from 'icons'; +import { ObjectType, ShapeType, ColorBy } from 'reducers/interfaces'; +import ColorPicker from './color-picker'; + +interface Props { + serverID: number | undefined; + locked: boolean; + shapeType: ShapeType; + objectType: ObjectType; + color: string; + colorBy: ColorBy; + colorPickerVisible: boolean; + changeColorShortcut: string; + copyShortcut: string; + pasteShortcut: string; + propagateShortcut: string; + toBackgroundShortcut: string; + toForegroundShortcut: string; + removeShortcut: string; + changeColor: (value: string) => void; + copy: (() => void); + remove: (() => void); + propagate: (() => void); + createURL: (() => void); + switchOrientation: (() => void); + toBackground: (() => void); + toForeground: (() => void); + resetCuboidPerspective: (() => void); + changeColorPickerVisible: (visible: boolean) => void; +} + +export default function ItemMenu(props: Props): JSX.Element { + const { + serverID, + locked, + shapeType, + objectType, + color, + colorBy, + colorPickerVisible, + changeColorShortcut, + copyShortcut, + pasteShortcut, + propagateShortcut, + toBackgroundShortcut, + toForegroundShortcut, + removeShortcut, + changeColor, + copy, + remove, + propagate, + createURL, + switchOrientation, + toBackground, + toForeground, + resetCuboidPerspective, + changeColorPickerVisible, + } = props; + + return ( + + + + + + + + + + + + + + + { [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && ( + + + + )} + {shapeType === ShapeType.CUBOID && ( + + + + )} + {objectType !== ObjectType.TAG && ( + + + + + + )} + {objectType !== ObjectType.TAG && ( + + + + + + )} + {[ColorBy.INSTANCE, ColorBy.GROUP].includes(colorBy) && ( + + + + + + + + )} + + + + + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 1a5b54cb..ce2e7a99 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -3,681 +3,17 @@ // SPDX-License-Identifier: MIT import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Icon from 'antd/lib/icon'; -import Select from 'antd/lib/select'; -import Radio, { RadioChangeEvent } from 'antd/lib/radio'; -import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; -import Input from 'antd/lib/input'; -import InputNumber from 'antd/lib/input-number'; -import Collapse from 'antd/lib/collapse'; -import Dropdown from 'antd/lib/dropdown'; -import Menu from 'antd/lib/menu'; -import Button from 'antd/lib/button'; -import Modal from 'antd/lib/modal'; -import Popover from 'antd/lib/popover'; -import Text from 'antd/lib/typography/Text'; -import Tooltip from 'antd/lib/tooltip'; -import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer'; -import consts from 'consts'; +import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons'; import { - ObjectOutsideIcon, - FirstIcon, - LastIcon, - PreviousIcon, - NextIcon, - BackgroundIcon, - ForegroundIcon, -} from 'icons'; -import { ObjectType, ShapeType } from 'reducers/interfaces'; -import { clamp } from 'utils/math'; + ObjectType, + ShapeType, + ColorBy, +} from 'reducers/interfaces'; +import ItemDetails, { attrValuesAreEqual } from './object-item-details'; +import ItemBasics from './object-item-basics'; -function ItemMenu( - serverID: number | undefined, - locked: boolean, - objectType: ObjectType, - copyShortcut: string, - pasteShortcut: string, - propagateShortcut: string, - toBackgroundShortcut: string, - toForegroundShortcut: string, - removeShortcut: string, - copy: (() => void), - remove: (() => void), - propagate: (() => void), - createURL: (() => void), - toBackground: (() => void), - toForeground: (() => void), -): JSX.Element { - return ( - - - - - - - - - - - - - - - { objectType !== ObjectType.TAG && ( - - - - - - )} - { objectType !== ObjectType.TAG && ( - - - - - - )} - - - - - - - ); -} - -interface ItemTopComponentProps { - clientID: number; - serverID: number | undefined; - labelID: number; - labels: any[]; - objectType: ObjectType; - type: string; - locked: boolean; - copyShortcut: string; - pasteShortcut: string; - propagateShortcut: string; - toBackgroundShortcut: string; - toForegroundShortcut: string; - removeShortcut: string; - changeLabel(labelID: string): void; - copy(): void; - remove(): void; - propagate(): void; - createURL(): void; - toBackground(): void; - toForeground(): void; -} - -function ItemTopComponent(props: ItemTopComponentProps): JSX.Element { - const { - clientID, - serverID, - labelID, - labels, - objectType, - type, - locked, - copyShortcut, - pasteShortcut, - propagateShortcut, - toBackgroundShortcut, - toForegroundShortcut, - removeShortcut, - changeLabel, - copy, - remove, - propagate, - createURL, - toBackground, - toForeground, - } = props; - - return ( - - - {clientID} -
- {type} - - - - - - - - - - - -
- ); -} - -const ItemTop = React.memo(ItemTopComponent); - -interface ItemButtonsComponentProps { - objectType: ObjectType; - shapeType: ShapeType; - occluded: boolean; - outside: boolean | undefined; - locked: boolean; - pinned: boolean; - hidden: boolean; - keyframe: boolean | undefined; - switchOccludedShortcut: string; - switchOutsideShortcut: string; - switchLockShortcut: string; - switchHiddenShortcut: string; - switchKeyFrameShortcut: string; - nextKeyFrameShortcut: string; - prevKeyFrameShortcut: string; - - navigateFirstKeyframe: null | (() => void); - navigatePrevKeyframe: null | (() => void); - navigateNextKeyframe: null | (() => void); - navigateLastKeyframe: null | (() => void); - - setOccluded(): void; - unsetOccluded(): void; - setOutside(): void; - unsetOutside(): void; - setKeyframe(): void; - unsetKeyframe(): void; - lock(): void; - unlock(): void; - pin(): void; - unpin(): void; - hide(): void; - show(): void; -} - -function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { - const { - objectType, - shapeType, - occluded, - outside, - locked, - pinned, - hidden, - keyframe, - switchOccludedShortcut, - switchOutsideShortcut, - switchLockShortcut, - switchHiddenShortcut, - switchKeyFrameShortcut, - nextKeyFrameShortcut, - prevKeyFrameShortcut, - - navigateFirstKeyframe, - navigatePrevKeyframe, - navigateNextKeyframe, - navigateLastKeyframe, - - setOccluded, - unsetOccluded, - setOutside, - unsetOutside, - setKeyframe, - unsetKeyframe, - lock, - unlock, - pin, - unpin, - hide, - show, - } = props; - - if (objectType === ObjectType.TRACK) { - return ( - - - - - { navigateFirstKeyframe - ? - : } - - - { navigatePrevKeyframe - ? ( - - - - ) - : } - - - { navigateNextKeyframe - ? ( - - - - ) - : } - - - { navigateLastKeyframe - ? - : } - - - - - - { outside - ? - : } - - - - - { locked - ? - : } - - - - - { occluded - ? - : } - - - - - - - - { keyframe - ? - : } - - - { - shapeType !== ShapeType.POINTS && ( - - - { pinned - ? - : } - - - ) - } - - - - ); - } - - if (objectType === ObjectType.TAG) { - return ( - - - - - - { locked - ? - : } - - - - - - ); - } - - return ( - - - - - - { locked - ? - : } - - - - - { occluded - ? - : } - - - - - - { - shapeType !== ShapeType.POINTS && ( - - - { pinned - ? - : } - - - ) - } - - - - ); -} - -const ItemButtons = React.memo(ItemButtonsComponent); - -interface ItemAttributeComponentProps { - attrInputType: string; - attrValues: string[]; - attrValue: string; - attrName: string; - attrID: number; - changeAttribute(attrID: number, value: string): void; -} - -function attrIsTheSame( - prevProps: ItemAttributeComponentProps, - nextProps: ItemAttributeComponentProps, -): boolean { - return nextProps.attrID === prevProps.attrID - && nextProps.attrValue === prevProps.attrValue - && nextProps.attrName === prevProps.attrName - && nextProps.attrInputType === prevProps.attrInputType - && nextProps.attrValues - .map((value: string, id: number): boolean => prevProps.attrValues[id] === value) - .every((value: boolean): boolean => value); -} - -function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element { - const { - attrInputType, - attrValues, - attrValue, - attrName, - attrID, - changeAttribute, - } = props; - - if (attrInputType === 'checkbox') { - return ( - - { - const value = event.target.checked ? 'true' : 'false'; - changeAttribute(attrID, value); - }} - > - - {attrName} - - - - ); - } - - if (attrInputType === 'radio') { - return ( - -
- - {attrName} - - { - changeAttribute(attrID, event.target.value); - }} - > - { attrValues.map((value: string): JSX.Element => ( - - {value === consts.UNDEFINED_ATTRIBUTE_VALUE - ? consts.NO_BREAK_SPACE : value} - - )) } - -
- - ); - } - - if (attrInputType === 'select') { - return ( - <> - - - {attrName} - - - - - - - ); - } - - if (attrInputType === 'number') { - const [min, max, step] = attrValues.map((value: string): number => +value); - - return ( - <> - - - {attrName} - - - - { - if (typeof (value) === 'number') { - changeAttribute( - attrID, `${clamp(value, min, max)}`, - ); - } - }} - value={+attrValue} - className='cvat-object-item-number-attribute' - min={min} - max={max} - step={step} - /> - - - ); - } - - return ( - <> - - - {attrName} - - - - ): void => { - changeAttribute(attrID, event.target.value); - }} - value={attrValue} - className='cvat-object-item-text-attribute' - /> - - - ); -} - -const ItemAttribute = React.memo(ItemAttributeComponent, attrIsTheSame); - - -interface ItemAttributesComponentProps { - collapsed: boolean; - attributes: any[]; - values: Record; - changeAttribute(attrID: number, value: string): void; - collapse(): void; -} - -function attrValuesAreEqual(next: Record, prev: Record): boolean { - const prevKeys = Object.keys(prev); - const nextKeys = Object.keys(next); - - return nextKeys.length === prevKeys.length - && nextKeys.map((key: string): boolean => prev[+key] === next[+key]) - .every((value: boolean) => value); -} - -function attrAreTheSame( - prevProps: ItemAttributesComponentProps, - nextProps: ItemAttributesComponentProps, -): boolean { - return nextProps.collapsed === prevProps.collapsed - && nextProps.attributes === prevProps.attributes - && attrValuesAreEqual(nextProps.values, prevProps.values); -} - -function ItemAttributesComponent(props: ItemAttributesComponentProps): JSX.Element { - const { - collapsed, - attributes, - values, - changeAttribute, - collapse, - } = props; - - const sorted = [...attributes] - .sort((a: any, b: any): number => a.inputType.localeCompare(b.inputType)); - - return ( - - - Details} - key='details' - > - { sorted.map((attribute: any): JSX.Element => ( - - - - ))} - - - - ); -} - -const ItemAttributes = React.memo(ItemAttributesComponent, attrAreTheSame); - interface Props { normalizedKeyMap: Record; activated: boolean; @@ -686,57 +22,33 @@ interface Props { clientID: number; serverID: number | undefined; labelID: number; - occluded: boolean; - outside: boolean | undefined; locked: boolean; - pinned: boolean; - hidden: boolean; - keyframe: boolean | undefined; attrValues: Record; color: string; - colors: string[]; + colorBy: ColorBy; labels: any[]; attributes: any[]; collapsed: boolean; - navigateFirstKeyframe: null | (() => void); - navigatePrevKeyframe: null | (() => void); - navigateNextKeyframe: null | (() => void); - navigateLastKeyframe: null | (() => void); activate(): void; copy(): void; propagate(): void; createURL(): void; + switchOrientation(): void; toBackground(): void; toForeground(): void; remove(): void; - setOccluded(): void; - unsetOccluded(): void; - setOutside(): void; - unsetOutside(): void; - setKeyframe(): void; - unsetKeyframe(): void; - lock(): void; - unlock(): void; - pin(): void; - unpin(): void; - hide(): void; - show(): void; changeLabel(labelID: string): void; changeAttribute(attrID: number, value: string): void; changeColor(color: string): void; collapse(): void; + resetCuboidPerspective(): void; } function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { return nextProps.activated === prevProps.activated && nextProps.locked === prevProps.locked - && nextProps.pinned === prevProps.pinned - && nextProps.occluded === prevProps.occluded - && nextProps.outside === prevProps.outside - && nextProps.hidden === prevProps.hidden - && nextProps.keyframe === prevProps.keyframe && nextProps.labelID === prevProps.labelID && nextProps.color === prevProps.color && nextProps.clientID === prevProps.clientID @@ -747,10 +59,7 @@ function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { && nextProps.labels === prevProps.labels && nextProps.attributes === prevProps.attributes && nextProps.normalizedKeyMap === prevProps.normalizedKeyMap - && nextProps.navigateFirstKeyframe === prevProps.navigateFirstKeyframe - && nextProps.navigatePrevKeyframe === prevProps.navigatePrevKeyframe - && nextProps.navigateNextKeyframe === prevProps.navigateNextKeyframe - && nextProps.navigateLastKeyframe === prevProps.navigateLastKeyframe + && nextProps.colorBy === prevProps.colorBy && attrValuesAreEqual(nextProps.attrValues, prevProps.attrValues); } @@ -761,49 +70,30 @@ function ObjectItemComponent(props: Props): JSX.Element { shapeType, clientID, serverID, - occluded, - outside, locked, - pinned, - hidden, - keyframe, attrValues, labelID, color, - colors, + colorBy, attributes, labels, collapsed, normalizedKeyMap, - navigateFirstKeyframe, - navigatePrevKeyframe, - navigateNextKeyframe, - navigateLastKeyframe, activate, copy, propagate, createURL, + switchOrientation, toBackground, toForeground, remove, - setOccluded, - unsetOccluded, - setOutside, - unsetOutside, - setKeyframe, - unsetKeyframe, - lock, - unlock, - pin, - unpin, - hide, - show, changeLabel, changeAttribute, changeColor, collapse, + resetCuboidPerspective, } = props; const type = objectType === ObjectType.TAG ? ObjectType.TAG.toUpperCase() @@ -813,34 +103,26 @@ function ObjectItemComponent(props: Props): JSX.Element { : 'cvat-objects-sidebar-state-item cvat-objects-sidebar-state-active-item'; return ( -
- - )} - > -
- +
+
- -
diff --git a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx index eb4e3796..d68d58d9 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx @@ -89,7 +89,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element { }); const makeShapesTracksTitle = (title: string): JSX.Element => ( - + {title} diff --git a/cvat-ui/src/components/change-password-modal/change-password-form.tsx b/cvat-ui/src/components/change-password-modal/change-password-form.tsx new file mode 100644 index 00000000..f1548daa --- /dev/null +++ b/cvat-ui/src/components/change-password-modal/change-password-form.tsx @@ -0,0 +1,166 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Form, { FormComponentProps } from 'antd/lib/form/Form'; +import Button from 'antd/lib/button'; +import Icon from 'antd/lib/icon'; +import Input from 'antd/lib/input'; + +import patterns from 'utils/validation-patterns'; + +export interface ChangePasswordData { + oldPassword: string; + newPassword1: string; + newPassword2: string; +} + +type ChangePasswordFormProps = { + fetching: boolean; + onSubmit(loginData: ChangePasswordData): void; +} & FormComponentProps; + +class ChangePasswordFormComponent extends React.PureComponent { + private validateConfirmation = (_: any, value: string, callback: Function): void => { + const { form } = this.props; + if (value && value !== form.getFieldValue('newPassword1')) { + callback('Two passwords that you enter is inconsistent!'); + } else { + callback(); + } + }; + + private validatePassword = (_: any, value: string, callback: Function): void => { + const { form } = this.props; + if (!patterns.validatePasswordLength.pattern.test(value)) { + callback(patterns.validatePasswordLength.message); + } + + if (!patterns.passwordContainsNumericCharacters.pattern.test(value)) { + callback(patterns.passwordContainsNumericCharacters.message); + } + + if (!patterns.passwordContainsUpperCaseCharacter.pattern.test(value)) { + callback(patterns.passwordContainsUpperCaseCharacter.message); + } + + if (!patterns.passwordContainsLowerCaseCharacter.pattern.test(value)) { + callback(patterns.passwordContainsLowerCaseCharacter.message); + } + + if (value) { + form.validateFields(['newPassword2'], { force: true }); + } + callback(); + }; + + private handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values): void => { + if (!error) { + const validatedFields = { + ...values, + confirmations: [], + }; + + onSubmit(validatedFields); + } + }); + }; + + private renderOldPasswordField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('oldPassword', { + rules: [{ + required: true, + message: 'Please input your current password!', + }], + })(} + placeholder='Current password' + />)} + + ); + } + + private renderNewPasswordField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('newPassword1', { + rules: [{ + required: true, + message: 'Please input new password!', + }, { + validator: this.validatePassword, + }], + })(} + placeholder='New password' + />)} + + ); + } + + private renderNewPasswordConfirmationField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('newPassword2', { + rules: [{ + required: true, + message: 'Please confirm your new password!', + }, { + validator: this.validateConfirmation, + }], + })(} + placeholder='Confirm new password' + />)} + + ); + } + + public render(): JSX.Element { + const { fetching } = this.props; + + return ( +
+ {this.renderOldPasswordField()} + {this.renderNewPasswordField()} + {this.renderNewPasswordConfirmationField()} + + + + +
+ ); + } +} + +export default Form.create()(ChangePasswordFormComponent); diff --git a/cvat-ui/src/components/change-password-modal/change-password-modal.tsx b/cvat-ui/src/components/change-password-modal/change-password-modal.tsx new file mode 100644 index 00000000..2f4f538b --- /dev/null +++ b/cvat-ui/src/components/change-password-modal/change-password-modal.tsx @@ -0,0 +1,84 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { connect } from 'react-redux'; +import Modal from 'antd/lib/modal'; +import Title from 'antd/lib/typography/Title'; + +import { changePasswordAsync } from 'actions/auth-actions'; +import { CombinedState } from 'reducers/interfaces'; +import ChangePasswordForm, { ChangePasswordData } from './change-password-form'; + + +interface StateToProps { + fetching: boolean; + visible: boolean; +} + +interface DispatchToProps { + onChangePassword( + oldPassword: string, + newPassword1: string, + newPassword2: string): void; +} + +interface ChangePasswordPageComponentProps { + fetching: boolean; + visible: boolean; + onChangePassword: (oldPassword: string, newPassword1: string, newPassword2: string) => void; + onClose(): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + return { + fetching: state.auth.fetching, + visible: state.auth.showChangePasswordDialog, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return ({ + onChangePassword(oldPassword: string, newPassword1: string, newPassword2: string): void { + dispatch(changePasswordAsync(oldPassword, newPassword1, newPassword2)); + }, + }); +} + +function ChangePasswordComponent(props: ChangePasswordPageComponentProps): JSX.Element { + const { + fetching, + onChangePassword, + visible, + onClose, + } = props; + + return ( + Change password} + okType='primary' + okText='Submit' + footer={null} + visible={visible} + destroyOnClose + onCancel={onClose} + > + { + onChangePassword( + changePasswordData.oldPassword, + changePasswordData.newPassword1, + changePasswordData.newPassword2, + ); + }} + fetching={fetching} + /> + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(ChangePasswordComponent); diff --git a/cvat-ui/src/components/create-model-page/create-model-content.tsx b/cvat-ui/src/components/create-model-page/create-model-content.tsx deleted file mode 100644 index a320b805..00000000 --- a/cvat-ui/src/components/create-model-page/create-model-content.tsx +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Icon from 'antd/lib/icon'; -import Alert from 'antd/lib/alert'; -import Button from 'antd/lib/button'; -import Tooltip from 'antd/lib/tooltip'; -import message from 'antd/lib/message'; -import notification from 'antd/lib/notification'; -import Text from 'antd/lib/typography/Text'; - -import consts from 'consts'; -import ConnectedFileManager, { - FileManagerContainer, -} from 'containers/file-manager/file-manager'; -import { ModelFiles } from 'reducers/interfaces'; - -import CreateModelForm, { - CreateModelForm as WrappedCreateModelForm, -} from './create-model-form'; - -interface Props { - createModel(name: string, files: ModelFiles, global: boolean): void; - isAdmin: boolean; - modelCreatingStatus: string; -} - -export default class CreateModelContent extends React.PureComponent { - private modelForm: WrappedCreateModelForm; - private fileManagerContainer: FileManagerContainer; - - public constructor(props: Props) { - super(props); - this.modelForm = null as any as WrappedCreateModelForm; - this.fileManagerContainer = null as any as FileManagerContainer; - } - - public componentDidUpdate(prevProps: Props): void { - const { modelCreatingStatus } = this.props; - - if (prevProps.modelCreatingStatus !== 'CREATED' - && modelCreatingStatus === 'CREATED') { - message.success('The model has been uploaded'); - this.modelForm.resetFields(); - this.fileManagerContainer.reset(); - } - } - - private handleSubmitClick = (): void => { - const { createModel } = this.props; - this.modelForm.submit() - .then((data) => { - const { - local, - share, - } = this.fileManagerContainer.getFiles(); - - const files = local.length ? local : share; - const grouppedFiles: ModelFiles = { - xml: '', - bin: '', - py: '', - json: '', - }; - - (files as any).reduce((acc: ModelFiles, value: File | string): ModelFiles => { - const name = typeof value === 'string' ? value : value.name; - const [extension] = name.split('.').reverse(); - if (extension in acc) { - acc[extension] = value; - } - - return acc; - }, grouppedFiles); - - if (Object.keys(grouppedFiles) - .map((key: string) => grouppedFiles[key]) - .filter((val) => !!val).length !== 4) { - notification.error({ - message: 'Could not upload a model', - description: 'Please, specify correct files', - }); - } else { - createModel(data.name, grouppedFiles, data.global); - } - }).catch(() => { - notification.error({ - message: 'Could not upload a model', - description: 'Please, check input fields', - }); - }); - }; - - public render(): JSX.Element { - const { - modelCreatingStatus, - } = this.props; - const loading = !!modelCreatingStatus - && modelCreatingStatus !== 'CREATED'; - const status = modelCreatingStatus - && modelCreatingStatus !== 'CREATED' ? modelCreatingStatus : ''; - - const { AUTO_ANNOTATION_GUIDE_URL } = consts; - return ( - - - - { - // false positive - // eslint-disable-next-line - window.open(AUTO_ANNOTATION_GUIDE_URL, '_blank'); - }} - type='question-circle' - /> - - - - { - this.modelForm = ref; - } - } - /> - - - * - Select files: - - - { - this.fileManagerContainer = container; - } - } - withRemote={false} - /> - - - {status && } - - - - - - ); - } -} diff --git a/cvat-ui/src/components/create-model-page/create-model-form.tsx b/cvat-ui/src/components/create-model-page/create-model-form.tsx deleted file mode 100644 index e1dbcb33..00000000 --- a/cvat-ui/src/components/create-model-page/create-model-form.tsx +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Form, { FormComponentProps } from 'antd/lib/form/Form'; -import Input from 'antd/lib/input'; -import Tooltip from 'antd/lib/tooltip'; -import Checkbox from 'antd/lib/checkbox'; -import Text from 'antd/lib/typography/Text'; - -type Props = FormComponentProps; - -export class CreateModelForm extends React.PureComponent { - public submit(): Promise<{name: string; global: boolean}> { - const { form } = this.props; - return new Promise((resolve, reject) => { - form.validateFields((errors, values): void => { - if (!errors) { - resolve({ - name: values.name, - global: values.global, - }); - } else { - reject(errors); - } - }); - }); - } - - public resetFields(): void { - const { form } = this.props; - form.resetFields(); - } - - public render(): JSX.Element { - const { form } = this.props; - const { getFieldDecorator } = form; - - return ( -
e.preventDefault()}> - - - * - Name: - - - - { getFieldDecorator('name', { - rules: [{ - required: true, - message: 'Please, specify a model name', - }], - })()} - - - - - - { getFieldDecorator('global', { - initialValue: false, - valuePropName: 'checked', - })( - - - Load globally - - , - )} - - - - -
- ); - } -} - -export default Form.create()(CreateModelForm); diff --git a/cvat-ui/src/components/create-model-page/create-model-page.tsx b/cvat-ui/src/components/create-model-page/create-model-page.tsx deleted file mode 100644 index 7c81aca0..00000000 --- a/cvat-ui/src/components/create-model-page/create-model-page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import './styles.scss'; -import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Text from 'antd/lib/typography/Text'; - -import { ModelFiles } from 'reducers/interfaces'; -import CreateModelContent from './create-model-content'; - -interface Props { - createModel(name: string, files: ModelFiles, global: boolean): void; - isAdmin: boolean; - modelCreatingStatus: string; -} - -export default function CreateModelPageComponent(props: Props): JSX.Element { - const { - isAdmin, - modelCreatingStatus, - createModel, - } = props; - - return ( - - - Upload a new model - - - - ); -} diff --git a/cvat-ui/src/components/create-model-page/styles.scss b/cvat-ui/src/components/create-model-page/styles.scss deleted file mode 100644 index 65df22b2..00000000 --- a/cvat-ui/src/components/create-model-page/styles.scss +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -@import '../../base.scss'; - -.cvat-create-model-form-wrapper { - text-align: center; - margin-top: 40px; - overflow-y: auto; - height: 90%; - - > div > span { - font-size: 36px; - } - - .cvat-create-model-content { - margin-top: 20px; - width: 100%; - height: auto; - border: 1px solid $border-color-1; - border-radius: 3px; - padding: 20px; - background: $background-color-1; - text-align: initial; - - > div:nth-child(1) > i { - float: right; - font-size: 20px; - color: $danger-icon-color; - } - - > div:nth-child(4) { - margin-top: 10px; - } - - > div:nth-child(6) > button { - margin-top: 10px; - float: right; - width: 120px; - } - } -} diff --git a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx index f97fa17e..b60b16e3 100644 --- a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx +++ b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx @@ -93,7 +93,7 @@ class AdvancedConfigurationForm extends React.PureComponent { delete filteredValues.frameStep; if (values.overlapSize && +values.segmentSize <= +values.overlapSize) { - reject(new Error('Overlap size must be more than segment size')); + reject(new Error('Segment size must be more than overlap size')); } if (typeof (values.startFrame) !== 'undefined' && typeof (values.stopFrame) !== 'undefined' @@ -142,7 +142,7 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Image quality}> - + {form.getFieldDecorator('imageQuality', { initialValue: 70, rules: [{ @@ -168,7 +168,7 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Overlap size}> - + {form.getFieldDecorator('overlapSize', { rules: [{ validator: isNonNegativeInteger, @@ -186,7 +186,7 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Segment size}> - + {form.getFieldDecorator('segmentSize', { rules: [{ validator: isPositiveInteger, @@ -403,6 +403,7 @@ class AdvancedConfigurationForm extends React.PureComponent { More: 1 - 4 )} + mouseLeaveDelay={0} > {form.getFieldDecorator('dataChunkSize', { rules: [{ diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx index 2bd32807..18de3d7a 100644 --- a/cvat-ui/src/components/create-task-page/create-task-content.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx @@ -3,6 +3,8 @@ // SPDX-License-Identifier: MIT import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; import { Row, Col } from 'antd/lib/grid'; import Alert from 'antd/lib/alert'; import Button from 'antd/lib/button'; @@ -10,7 +12,7 @@ import Collapse from 'antd/lib/collapse'; import notification from 'antd/lib/notification'; import Text from 'antd/lib/typography/Text'; -import FileManagerContainer from 'containers/file-manager/file-manager'; +import ConnectedFileManager from 'containers/file-manager/file-manager'; import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form'; import AdvancedConfigurationForm, { AdvancedConfiguration } from './advanced-configuration-form'; import LabelsEditor from '../labels-editor/labels-editor'; @@ -26,6 +28,7 @@ export interface CreateTaskData { interface Props { onCreate: (data: CreateTaskData) => void; status: string; + taskId: number | null; installedGit: boolean; } @@ -48,22 +51,31 @@ const defaultState = { }, }; -export default class CreateTaskContent extends React.PureComponent { +class CreateTaskContent extends React.PureComponent { private basicConfigurationComponent: any; private advancedConfigurationComponent: any; private fileManagerContainer: any; - public constructor(props: Props) { + public constructor(props: Props & RouteComponentProps) { super(props); this.state = { ...defaultState }; } public componentDidUpdate(prevProps: Props): void { - const { status } = this.props; + const { status, history, taskId } = this.props; if (status === 'CREATED' && prevProps.status !== 'CREATED') { + const btn = ( + + ); + notification.info({ message: 'The task has been created', + btn, }); this.basicConfigurationComponent.resetFields(); @@ -184,7 +196,7 @@ export default class CreateTaskContent extends React.PureComponent * Select files: - { this.fileManagerContainer = container; } } @@ -252,3 +264,5 @@ export default class CreateTaskContent extends React.PureComponent ); } } + +export default withRouter(CreateTaskContent); diff --git a/cvat-ui/src/components/create-task-page/create-task-page.tsx b/cvat-ui/src/components/create-task-page/create-task-page.tsx index 15c5f18c..39549ea6 100644 --- a/cvat-ui/src/components/create-task-page/create-task-page.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-page.tsx @@ -16,6 +16,7 @@ interface Props { onCreate: (data: CreateTaskData) => void; status: string; error: string; + taskId: number | null; installedGit: boolean; } @@ -23,6 +24,7 @@ export default function CreateTaskPage(props: Props): JSX.Element { const { error, status, + taskId, onCreate, installedGit, } = props; @@ -66,6 +68,7 @@ export default function CreateTaskPage(props: Props): JSX.Element { Create a new task div > span { font-size: 36px; diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index ad83d05b..5c1af2cf 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -10,24 +10,26 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { GlobalHotKeys, ExtendedKeyMapOptions, configure } from 'react-hotkeys'; import Spin from 'antd/lib/spin'; import Layout from 'antd/lib/layout'; +import { Row, Col } from 'antd/lib/grid'; +import Text from 'antd/lib/typography/Text'; import notification from 'antd/lib/notification'; import GlobalErrorBoundary from 'components/global-error-boundary/global-error-boundary'; import ShorcutsDialog from 'components/shortcuts-dialog/shortcuts-dialog'; -import SettingsPageContainer from 'containers/settings-page/settings-page'; import TasksPageContainer from 'containers/tasks-page/tasks-page'; import CreateTaskPageContainer from 'containers/create-task-page/create-task-page'; import TaskPageContainer from 'containers/task-page/task-page'; import ModelsPageContainer from 'containers/models-page/models-page'; -import CreateModelPageContainer from 'containers/create-model-page/create-model-page'; import AnnotationPageContainer from 'containers/annotation-page/annotation-page'; import LoginPageContainer from 'containers/login-page/login-page'; import RegisterPageContainer from 'containers/register-page/register-page'; -import HeaderContainer from 'containers/header/header'; +import Header from 'components/header/header'; import { customWaViewHit } from 'utils/enviroment'; +import showPlatformNotification, { stopNotifications, platformInfo } from 'utils/platform-checker'; import getCore from 'cvat-core-wrapper'; import { NotificationsState } from 'reducers/interfaces'; +import Modal from 'antd/lib/modal'; interface CVATAppProps { loadFormats: () => void; @@ -36,25 +38,30 @@ interface CVATAppProps { verifyAuthorized: () => void; loadUserAgreements: () => void; initPlugins: () => void; + initModels: () => void; resetErrors: () => void; resetMessages: () => void; switchShortcutsDialog: () => void; + switchSettingsDialog: () => void; + loadAuthActions: () => void; keyMap: Record; userInitialized: boolean; userFetching: boolean; pluginsInitialized: boolean; pluginsFetching: boolean; + modelsInitialized: boolean; + modelsFetching: boolean; formatsInitialized: boolean; formatsFetching: boolean; usersInitialized: boolean; usersFetching: boolean; aboutInitialized: boolean; aboutFetching: boolean; - installedAutoAnnotation: boolean; - installedTFAnnotation: boolean; - installedTFSegmentation: boolean; - userAgreementsFetching: boolean, - userAgreementsInitialized: boolean, + userAgreementsFetching: boolean; + userAgreementsInitialized: boolean; + authActionsFetching: boolean; + authActionsInitialized: boolean; + allowChangePassword: boolean; notifications: NotificationsState; user: any; } @@ -62,7 +69,7 @@ interface CVATAppProps { class CVATApplication extends React.PureComponent { public componentDidMount(): void { const core = getCore(); - const { verifyAuthorized, history } = this.props; + const { verifyAuthorized, history, location } = this.props; configure({ ignoreRepeatedEventsWhenKeyHeldDown: false }); // Logger configuration @@ -73,8 +80,8 @@ class CVATApplication extends React.PureComponent window.document.hasFocus, userActivityCallback); customWaViewHit(location.pathname, location.search, location.hash); - history.listen((location) => { - customWaViewHit(location.pathname, location.search, location.hash); + history.listen((_location) => { + customWaViewHit(_location.pathname, _location.search, _location.hash); }); verifyAuthorized(); @@ -88,6 +95,8 @@ class CVATApplication extends React.PureComponent 200 ? 'Open the Browser Console to get details' : error, }); + // eslint-disable-next-line no-console console.error(error); } @@ -199,8 +221,8 @@ class CVATApplication extends React.PureComponent { - if (event) { - event.preventDefault(); - } + if (event) event.preventDefault(); switchShortcutsDialog(); }, - OPEN_SETTINGS: (event: KeyboardEvent | undefined) => { - if (event) { - event.preventDefault(); - } + SWITCH_SETTINGS: (event: KeyboardEvent | undefined) => { + if (event) event.preventDefault(); - if (history.location.pathname.endsWith('settings')) { - history.goBack(); - } else { - history.push('/settings'); - } + switchSettingsDialog(); }, }; + if (showPlatformNotification()) { + stopNotifications(false); + const info = platformInfo(); + Modal.warning({ + title: 'Unsupported platform detected', + content: ( + <> + + + + {`The browser you are using is ${info.name} ${info.version} based on ${info.engine} .` + + ' CVAT was tested in the latest versions of Chrome and Firefox.' + + ' We recommend to use Chrome (or another Chromium based browser)'} + + + + + + + {`The operating system is ${info.os}`} + + + + + ), + onOk: () => stopNotifications(true), + }); + } + + if (readyForRender) { - if (user) { + if (user && user.isVerified) { return ( - - +
+ - - {withModels - && } - {installedAutoAnnotation - && } + diff --git a/cvat-ui/src/components/feedback/styles.scss b/cvat-ui/src/components/feedback/styles.scss index 9a62fe18..39415886 100644 --- a/cvat-ui/src/components/feedback/styles.scss +++ b/cvat-ui/src/components/feedback/styles.scss @@ -3,13 +3,12 @@ // SPDX-License-Identifier: MIT .cvat-feedback-button { - position: absolute; + position: fixed; bottom: 20px; right: 20px; - padding: 0px; -} + padding: 0; + height: auto; -.cvat-feedback-button { > i { font-size: 40px; } diff --git a/cvat-ui/src/components/file-manager/file-manager.tsx b/cvat-ui/src/components/file-manager/file-manager.tsx index 3e2f2551..3c970cd5 100644 --- a/cvat-ui/src/components/file-manager/file-manager.tsx +++ b/cvat-ui/src/components/file-manager/file-manager.tsx @@ -129,6 +129,8 @@ export default class FileManager extends React.PureComponent { private renderShareSelector(): JSX.Element { function renderTreeNodes(data: TreeNodeNormal[]): JSX.Element[] { + // sort alphabetically + data.sort((a: TreeNodeNormal, b: TreeNodeNormal): number => a.key.localeCompare(b.key)); return data.map((item: TreeNodeNormal) => { if (item.children) { return ( diff --git a/cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx b/cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx index aeee46b7..9be93aae 100644 --- a/cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx +++ b/cvat-ui/src/components/global-error-boundary/global-error-boundary.tsx @@ -5,8 +5,6 @@ import './styles.scss'; import React from 'react'; import { connect } from 'react-redux'; -import { Action } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; import Result from 'antd/lib/result'; import Text from 'antd/lib/typography/Text'; import Paragraph from 'antd/lib/typography/Paragraph'; @@ -16,6 +14,7 @@ import Tooltip from 'antd/lib/tooltip'; import copy from 'copy-to-clipboard'; import ErrorStackParser from 'error-stack-parser'; +import { ThunkDispatch } from 'utils/redux'; import { resetAfterErrorAsync } from 'actions/boundaries-actions'; import { CombinedState } from 'reducers/interfaces'; import logger, { LogType } from 'cvat-logger'; @@ -60,7 +59,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } -function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { +function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { return { restore(): void { dispatch(resetAfterErrorAsync()); @@ -154,7 +153,7 @@ class GlobalErrorBoundary extends React.PureComponent {
  • - + {/* eslint-disable-next-line */} {copy(message)}}> Copy diff --git a/cvat-ui/src/components/global-error-boundary/styles.scss b/cvat-ui/src/components/global-error-boundary/styles.scss index c209a40a..a1666b2c 100644 --- a/cvat-ui/src/components/global-error-boundary/styles.scss +++ b/cvat-ui/src/components/global-error-boundary/styles.scss @@ -13,5 +13,3 @@ color: red; } } - - diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index 38925dbb..379e7c70 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -4,8 +4,8 @@ import './styles.scss'; import React from 'react'; -import { RouteComponentProps } from 'react-router'; -import { withRouter } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { useHistory } from 'react-router'; import { Row, Col } from 'antd/lib/grid'; import Layout from 'antd/lib/layout'; import Icon from 'antd/lib/icon'; @@ -15,52 +15,134 @@ import Dropdown from 'antd/lib/dropdown'; import Modal from 'antd/lib/modal'; import Text from 'antd/lib/typography/Text'; -import { CVATLogo, AccountIcon } from 'icons'; +import getCore from 'cvat-core-wrapper'; import consts from 'consts'; -interface HeaderContainerProps { - onLogout: () => void; +import { CVATLogo, AccountIcon } from 'icons'; +import ChangePasswordDialog from 'components/change-password-modal/change-password-modal'; +import { switchSettingsDialog as switchSettingsDialogAction } from 'actions/settings-actions'; +import { logoutAsync, authActions } from 'actions/auth-actions'; +import { SupportedPlugins, CombinedState } from 'reducers/interfaces'; +import SettingsModal from './settings-modal/settings-modal'; + +const core = getCore(); + +interface Tool { + name: string; + description: string; + server: { + host: string; + version: string; + }; + core: { + version: string; + }; + canvas: { + version: string; + }; + ui: { + version: string; + }; +} + +interface StateToProps { + user: any; + tool: Tool; + switchSettingsShortcut: string; + settingsDialogShown: boolean; + changePasswordDialogShown: boolean; + changePasswordFetching: boolean; logoutFetching: boolean; installedAnalytics: boolean; - installedAutoAnnotation: boolean; - installedTFAnnotation: boolean; - installedTFSegmentation: boolean; - serverHost: string; - username: string; - toolName: string; - serverVersion: string; - serverDescription: string; - coreVersion: string; - canvasVersion: string; - uiVersion: string; - switchSettingsShortcut: string; + renderChangePasswordItem: boolean; } -type Props = HeaderContainerProps & RouteComponentProps; +interface DispatchToProps { + onLogout: () => void; + switchSettingsDialog: (show: boolean) => void; + switchChangePasswordDialog: (show: boolean) => void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + auth: { + user, + fetching: logoutFetching, + fetching: changePasswordFetching, + showChangePasswordDialog: changePasswordDialogShown, + allowChangePassword: renderChangePasswordItem, + }, + plugins: { + list, + }, + about: { + server, + packageVersion, + }, + shortcuts: { + normalizedKeyMap, + }, + settings: { + showDialog: settingsDialogShown, + }, + } = state; + + return { + user, + tool: { + name: server.name as string, + description: server.description as string, + server: { + host: core.config.backendAPI.slice(0, -7), + version: server.version as string, + }, + canvas: { + version: packageVersion.canvas, + }, + core: { + version: packageVersion.core, + }, + ui: { + version: packageVersion.ui, + }, + }, + switchSettingsShortcut: normalizedKeyMap.SWITCH_SETTINGS, + settingsDialogShown, + changePasswordDialogShown, + changePasswordFetching, + logoutFetching, + installedAnalytics: list[SupportedPlugins.ANALYTICS], + renderChangePasswordItem, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onLogout: (): void => dispatch(logoutAsync()), + switchSettingsDialog: (show: boolean): void => dispatch(switchSettingsDialogAction(show)), + switchChangePasswordDialog: (show: boolean): void => ( + dispatch(authActions.switchChangePasswordDialog(show)) + ), + }; +} + +type Props = StateToProps & DispatchToProps; function HeaderContainer(props: Props): JSX.Element { const { - installedTFSegmentation, - installedAutoAnnotation, - installedTFAnnotation, + user, + tool, installedAnalytics, - username, - toolName, - serverHost, - serverVersion, - serverDescription, - coreVersion, - canvasVersion, - uiVersion, - onLogout, logoutFetching, + changePasswordFetching, + settingsDialogShown, switchSettingsShortcut, + onLogout, + switchSettingsDialog, + switchChangePasswordDialog, + renderChangePasswordItem, } = props; - const renderModels = installedAutoAnnotation - || installedTFAnnotation - || installedTFSegmentation; - const { CHANGELOG_URL, LICENSE_URL, @@ -69,20 +151,22 @@ function HeaderContainer(props: Props): JSX.Element { GITHUB_URL, } = consts; - function aboutModal(): void { + const history = useHistory(); + + function showAboutModal(): void { Modal.info({ - title: `${toolName}`, + title: `${tool.name}`, content: (

    - {`${serverDescription}`} + {`${tool.description}`}

    Server version: - {` ${serverVersion}`} + {` ${tool.server.version}`}

    @@ -90,7 +174,7 @@ function HeaderContainer(props: Props): JSX.Element { Core version: - {` ${coreVersion}`} + {` ${tool.core.version}`}

    @@ -98,7 +182,7 @@ function HeaderContainer(props: Props): JSX.Element { Canvas version: - {` ${canvasVersion}`} + {` ${tool.canvas.version}`}

    @@ -106,7 +190,7 @@ function HeaderContainer(props: Props): JSX.Element { UI version: - {` ${uiVersion}`} + {` ${tool.ui.version}`}

    @@ -128,19 +212,40 @@ function HeaderContainer(props: Props): JSX.Element { const menu = ( + {user.isStaff && ( + { + // false positive + // eslint-disable-next-line + window.open(`${tool.server.host}/admin`, '_blank'); + }} + > + + Admin page + + )} + props.history.push('/settings') - } + onClick={() => switchSettingsDialog(true)} > Settings - aboutModal()}> + About + {renderChangePasswordItem && ( + switchChangePasswordDialog(true)} + disabled={changePasswordFetching} + > + {changePasswordFetching ? : } + Change password + + )} + props.history.push('/tasks?page=1') + (event: React.MouseEvent): void => { + event.preventDefault(); + history.push('/tasks?page=1'); + } } > Tasks - { renderModels - && ( - - )} + { installedAnalytics && (
    + switchSettingsDialog(false)} + /> + { renderChangePasswordItem + && ( + switchChangePasswordDialog(false)} + /> + )} + ); } -export default withRouter(HeaderContainer); +function propsAreTheSame(prevProps: Props, nextProps: Props): boolean { + let equal = true; + for (const prop in nextProps) { + if (prop in prevProps && (prevProps as any)[prop] !== (nextProps as any)[prop]) { + if (prop !== 'tool') { + equal = false; + } + } + } + + return equal; +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(React.memo(HeaderContainer, propsAreTheSame)); diff --git a/cvat-ui/src/components/settings-page/player-settings.tsx b/cvat-ui/src/components/header/settings-modal/player-settings.tsx similarity index 71% rename from cvat-ui/src/components/settings-page/player-settings.tsx rename to cvat-ui/src/components/header/settings-modal/player-settings.tsx index 6b834122..ef872f67 100644 --- a/cvat-ui/src/components/settings-page/player-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/player-settings.tsx @@ -9,13 +9,16 @@ import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; import Button from 'antd/lib/button'; import Slider from 'antd/lib/slider'; import Select from 'antd/lib/select'; +import Popover from 'antd/lib/popover'; import InputNumber from 'antd/lib/input-number'; import Icon from 'antd/lib/icon'; import Text from 'antd/lib/typography/Text'; +import { CompactPicker } from 'react-color'; import { clamp } from 'utils/math'; import { BackJumpIcon, ForwardJumpIcon } from 'icons'; import { FrameSpeed, GridColor } from 'reducers/interfaces'; +import consts from 'consts'; interface Props { @@ -30,6 +33,7 @@ interface Props { brightnessLevel: number; contrastLevel: number; saturationLevel: number; + canvasBackgroundColor: string; onChangeFrameStep(step: number): void; onChangeFrameSpeed(speed: FrameSpeed): void; onSwitchResetZoom(enabled: boolean): void; @@ -41,6 +45,7 @@ interface Props { onChangeBrightnessLevel(level: number): void; onChangeContrastLevel(level: number): void; onChangeSaturationLevel(level: number): void; + onChangeCanvasBackgroundColor(color: string): void; } export default function PlayerSettingsComponent(props: Props): JSX.Element { @@ -56,6 +61,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { brightnessLevel, contrastLevel, saturationLevel, + canvasBackgroundColor, onChangeFrameStep, onChangeFrameSpeed, onSwitchResetZoom, @@ -67,6 +73,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { onChangeBrightnessLevel, onChangeContrastLevel, onChangeSaturationLevel, + onChangeCanvasBackgroundColor, } = props; const minFrameStep = 2; @@ -74,7 +81,6 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { const minGridSize = 5; const maxGridSize = 1000; - return (
    @@ -122,6 +128,23 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { + + + onChangeCanvasBackgroundColor(e.hex)} + /> + )} + overlayClassName='canvas-background-color-picker-popover' + trigger='click' + > + + + + - - - Brightness - - - { - onChangeBrightnessLevel(value as number); - }} - /> - - - - - Contrast - - - { - onChangeContrastLevel(value as number); - }} - /> - - - - - Saturation - - - { - onChangeSaturationLevel(value as number); - }} - /> - - - - - + + + + + Brightness + + + { + onChangeBrightnessLevel(value as number); + }} + /> + + + + + Contrast + + + { + onChangeContrastLevel(value as number); + }} + /> + + + + + Saturation + + + { + onChangeSaturationLevel(value as number); + }} + /> + + + + + + +
    diff --git a/cvat-ui/src/components/header/settings-modal/settings-modal.tsx b/cvat-ui/src/components/header/settings-modal/settings-modal.tsx new file mode 100644 index 00000000..b3c3503a --- /dev/null +++ b/cvat-ui/src/components/header/settings-modal/settings-modal.tsx @@ -0,0 +1,74 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; +import Tabs from 'antd/lib/tabs'; +import Icon from 'antd/lib/icon'; +import Text from 'antd/lib/typography/Text'; +import Modal from 'antd/lib/modal/Modal'; + +import WorkspaceSettingsContainer from 'containers/header/settings-modal/workspace-settings'; +import PlayerSettingsContainer from 'containers/header/settings-modal/player-settings'; +import Button from 'antd/lib/button'; + +interface SettingsModalProps { + visible: boolean; + onClose(): void; +} + +const SettingsModal = (props: SettingsModalProps): JSX.Element => { + const { visible, onClose } = props; + + return ( + + Close + + )} + > +
    + + + + Player + + ) + } + key='player' + > + + + + + Workspace + + ) + } + key='workspace' + > + + + +
    +
    + ); +}; + +export default SettingsModal; diff --git a/cvat-ui/src/components/settings-page/styles.scss b/cvat-ui/src/components/header/settings-modal/styles.scss similarity index 76% rename from cvat-ui/src/components/settings-page/styles.scss rename to cvat-ui/src/components/header/settings-modal/styles.scss index 4858af60..f0d23d20 100644 --- a/cvat-ui/src/components/settings-page/styles.scss +++ b/cvat-ui/src/components/header/settings-modal/styles.scss @@ -2,24 +2,25 @@ // // SPDX-License-Identifier: MIT -@import '../../base.scss'; +@import '../../../base.scss'; -.cvat-settings-page { +.cvat-settings-tabs { height: 100%; overflow-y: auto; padding-bottom: 15px; > div:nth-child(1) { - margin-top: 30px; + margin-top: 10px; margin-bottom: 10px; } } -.cvat-workspace-settings, .cvat-player-settings { +.cvat-workspace-settings, +.cvat-player-settings { width: 100%; height: max-content; background: $background-color-1; - padding: 50px; + padding: 24px; } .cvat-player-settings-grid, @@ -41,6 +42,7 @@ .cvat-player-settings-speed, .cvat-player-settings-reset-zoom, .cvat-player-settings-rotate-all, +.cvat-player-settings-canvas-background, .cvat-workspace-settings-aam-zoom-margin, .cvat-workspace-settings-auto-save-interval { margin-bottom: 25px; @@ -74,7 +76,7 @@ } .cvat-player-settings-step > div > span > i { - margin: 0px 5px; + margin: 0 5px; font-size: 10px; } @@ -95,12 +97,20 @@ } } -.cvat-settings-page-back-button { - width: 100px; - margin-top: 15px; +.cvat-player-settings-image-preview { + width: 100%; + max-height: 180px; + object-fit: cover; +} + +.canvas-background-color-picker-popover .ant-popover-inner-content { + padding: 6px 12px; + + :first-child:first-child { + box-shadow: unset !important; + } } -.cvat-settings-page-back-button-wrapper { - display: flex; - justify-content: flex-end; +.cvat-settings-modal .ant-modal-body { + padding-top: 0; } diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx similarity index 99% rename from cvat-ui/src/components/settings-page/workspace-settings.tsx rename to cvat-ui/src/components/header/settings-modal/workspace-settings.tsx index 5c85b529..f07b3dcc 100644 --- a/cvat-ui/src/components/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/workspace-settings.tsx @@ -42,7 +42,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { onSwitchAutomaticBordering, } = props; - const minAutoSaveInterval = 5; + const minAutoSaveInterval = 1; const maxAutoSaveInterval = 60; const minAAMMargin = 0; const maxAAMMargin = 1000; diff --git a/cvat-ui/src/components/header/styles.scss b/cvat-ui/src/components/header/styles.scss index d5c127bf..134cb488 100644 --- a/cvat-ui/src/components/header/styles.scss +++ b/cvat-ui/src/components/header/styles.scss @@ -6,9 +6,9 @@ .cvat-header.ant-layout-header { display: flex; - padding-left: 0px; - padding-right: 0px; - line-height: 0px; + padding-left: 0; + padding-right: 0; + line-height: 0; height: 44px; background: $header-color; } @@ -35,16 +35,15 @@ .ant-btn.cvat-header-button { color: $text-color; - padding: 0px 10px; + padding: 0 10px; margin-right: 10px; } - .ant-dropdown-trigger.cvat-header-menu-dropdown { display: flex; align-items: center; border-left: 1px solid $border-color-1; - padding: 0px 20px; + padding: 0 20px; } .anticon.cvat-header-account-icon { @@ -55,5 +54,5 @@ .anticon.cvat-header-menu-icon { margin-left: 16px; - margin-right: 0px; + margin-right: 0; } diff --git a/cvat-ui/src/components/labels-editor/common.ts b/cvat-ui/src/components/labels-editor/common.ts index 5ea21f53..a50765da 100644 --- a/cvat-ui/src/components/labels-editor/common.ts +++ b/cvat-ui/src/components/labels-editor/common.ts @@ -12,12 +12,81 @@ export interface Attribute { export interface Label { name: string; + color: string; id: number; attributes: Attribute[]; } let id = 0; +function validateParsedAttribute(attr: Attribute): void { + if (typeof (attr.name) !== 'string') { + throw new Error(`Type of attribute name must be a string. Got value ${attr.name}`); + } + + if (!['number', 'undefined'].includes(typeof (attr.id))) { + throw new Error(`Attribute: "${attr.name}". ` + + `Type of attribute id must be a number or undefined. Got value ${attr.id}`); + } + + if (!['checkbox', 'number', 'text', 'radio', 'select'].includes((attr.input_type || '').toLowerCase())) { + throw new Error(`Attribute: "${attr.name}". ` + + `Unknown input type: ${attr.input_type}`); + } + + if (typeof (attr.mutable) !== 'boolean') { + throw new Error(`Attribute: "${attr.name}". ` + + `Mutable flag must be a boolean value. Got value ${attr.mutable}`); + } + + if (!Array.isArray(attr.values)) { + throw new Error(`Attribute: "${attr.name}". ` + + `Attribute values must be an array. Got type ${typeof (attr.values)}`); + } + + if (!attr.values.length) { + throw new Error(`Attribute: "${attr.name}". Attribute values array mustn't be empty`); + } + + + for (const value of attr.values) { + if (typeof (value) !== 'string') { + throw new Error(`Attribute: "${attr.name}". ` + + `Each value must be a string. Got value ${value}`); + } + } +} + +export function validateParsedLabel(label: Label): void { + if (typeof (label.name) !== 'string') { + throw new Error(`Type of label name must be a string. Got value ${label.name}`); + } + + if (!['number', 'undefined'].includes(typeof (label.id))) { + throw new Error(`Label "${label.name}". ` + + `Type of label id must be only a number or undefined. Got value ${label.id}`); + } + + if (typeof (label.color) !== 'string') { + throw new Error(`Label "${label.name}". ` + + `Label color must be a string. Got ${typeof (label.color)}`); + } + + if (!label.color.match(/^#[0-9a-f]{6}$|^$/)) { + throw new Error(`Label "${label.name}". ` + + `Type of label color must be only a valid color string. Got value ${label.color}`); + } + + if (!Array.isArray(label.attributes)) { + throw new Error(`Label "${label.name}". ` + + `attributes must be an array. Got type ${typeof (label.attributes)}`); + } + + for (const attr of label.attributes) { + validateParsedAttribute(attr); + } +} + export function idGenerator(): number { return --id; } diff --git a/cvat-ui/src/components/labels-editor/constructor-creator.tsx b/cvat-ui/src/components/labels-editor/constructor-creator.tsx index b5c8664d..ff101b8a 100644 --- a/cvat-ui/src/components/labels-editor/constructor-creator.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-creator.tsx @@ -8,14 +8,30 @@ import LabelForm from './label-form'; import { Label } from './common'; interface Props { + labelNames: string[]; onCreate: (label: Label | null) => void; } -export default function ConstructorCreator(props: Props): JSX.Element { - const { onCreate } = props; +function compareProps(prevProps: Props, nextProps: Props): boolean { + if (prevProps.onCreate !== nextProps.onCreate) { + return false; + } + if (!(prevProps.labelNames.length === nextProps.labelNames.length + && prevProps.labelNames.map((value, index) => value === nextProps.labelNames[index]) + .reduce((prevValue, curValue) => prevValue && curValue, true) + )) { + return false; + } + return true; +} + +function ConstructorCreator(props: Props): JSX.Element { + const { onCreate, labelNames } = props; return (
    - +
    ); } + +export default React.memo(ConstructorCreator, compareProps); diff --git a/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx b/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx index a21962e0..4e0b4f17 100644 --- a/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx @@ -7,6 +7,7 @@ import Icon from 'antd/lib/icon'; import Tooltip from 'antd/lib/tooltip'; import Text from 'antd/lib/typography/Text'; +import consts from 'consts'; import { Label } from './common'; interface ConstructorViewerItemProps { @@ -25,9 +26,9 @@ export default function ConstructorViewerItem(props: ConstructorViewerItemProps) } = props; return ( -
    +
    {label.name} - + { label.id < 0 && ( - + void; } -const colors = [ - '#ff811e', '#9013fe', '#0074d9', - '#549ca4', '#e8c720', '#3d9970', - '#6b2034', '#2c344c', '#2ecc40', -]; - -let currentColor = 0; - -function nextColor(): string { - const color = colors[currentColor]; - currentColor += 1; - if (currentColor >= colors.length) { - currentColor = 0; - } - return color; -} - export default function ConstructorViewer(props: ConstructorViewerProps): JSX.Element { const { onCreate } = props; - currentColor = 0; - const list = [ @@ -416,7 +440,7 @@ class LabelForm extends React.PureComponent { private renderDoneButton(): JSX.Element { return ( - + + + , + ) + } + + + ); + } + public render(): JSX.Element { const { label, @@ -496,6 +551,8 @@ class LabelForm extends React.PureComponent { { this.renderLabelNameInput() } + { this.renderChangeColorButton() } + { this.renderNewAttributeButton() } { attributeItems.length > 0 diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 465c3a67..8c0ad072 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -64,6 +64,7 @@ export default class LabelsEditor return { name: label.name, id: label.id || idGenerator(), + color: label.color, attributes: label.attributes.map((attr: any): Attribute => ( { id: attr.id || idGenerator(), @@ -198,6 +199,7 @@ export default class LabelsEditor return { name: label.name, id: label.id < 0 ? undefined : label.id, + color: label.color, attributes: label.attributes.map((attr: Attribute): any => ( { name: attr.name, @@ -221,6 +223,7 @@ export default class LabelsEditor } public render(): JSX.Element { + const { labels } = this.props; const { savedLabels, unsavedLabels, @@ -235,7 +238,7 @@ export default class LabelsEditor tabBarStyle={{ marginBottom: '0px' }} tabBarExtraContent={( <> - +
    + ); + } + private renderContent(): JSX.Element { const { selectedModel, - cleanOut, + cleanup, mapping, } = this.state; const { @@ -311,8 +360,9 @@ export default class ModelRunnerModalComponent extends React.PureComponent _model.name === selectedModel)[0]; const excludedModelLabels: string[] = Object.keys(mapping); - const withMapping = model && !model.primary; - const tags = withMapping ? excludedModelLabels + const isDetector = model && model.type === 'detector'; + const isReId = model && model.type === 'reid'; + const tags = isDetector ? excludedModelLabels .map((modelLabel: string) => this.renderMappingTag( modelLabel, mapping[modelLabel], @@ -332,23 +382,24 @@ export default class ModelRunnerModalComponent extends React.PureComponent { this.renderModelSelector() } - { withMapping && tags} - { withMapping + { isDetector && tags} + { isDetector && mappingISAvailable && this.renderMappingInput(availableModelLabels, taskLabels)} - { withMapping + { isDetector && (
    this.setState({ - cleanOut: e.target.checked, + cleanup: e.target.checked, })} > Clean old annotations
    )} + { isReId && this.renderReidContent() }
    ); } @@ -357,14 +408,15 @@ export default class ModelRunnerModalComponent extends React.PureComponent model.name === selectedModel, )[0]; - const enabledSubmit = (!!activeModel - && activeModel.primary) || !!Object.keys(mapping).length; + const enabledSubmit = !!activeModel && (activeModel.type === 'reid' + || !!Object.keys(mapping).length); return ( visible && ( @@ -387,8 +439,13 @@ export default class ModelRunnerModalComponent extends React.PureComponent model.name === selectedModel)[0], - mapping, - cleanOut, + activeModel.type === 'detector' ? { + mapping, + cleanup, + } : { + threshold, + max_distance: maxDistance, + }, ); closeDialog(); }} @@ -397,9 +454,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent - {!modelsInitialized - && } - {modelsInitialized && this.renderContent()} + { this.renderContent() } ) ); diff --git a/cvat-ui/src/components/models-page/built-model-item.tsx b/cvat-ui/src/components/models-page/built-model-item.tsx index 83dda54e..f100dfe5 100644 --- a/cvat-ui/src/components/models-page/built-model-item.tsx +++ b/cvat-ui/src/components/models-page/built-model-item.tsx @@ -20,7 +20,7 @@ export default function BuiltModelItemComponent(props: Props): JSX.Element { return ( - Tensorflow + {model.framework} diff --git a/cvat-ui/src/components/models-page/deployed-model-item.tsx b/cvat-ui/src/components/models-page/deployed-model-item.tsx new file mode 100644 index 00000000..69ad3fcd --- /dev/null +++ b/cvat-ui/src/components/models-page/deployed-model-item.tsx @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row, Col } from 'antd/lib/grid'; +import Tag from 'antd/lib/tag'; +import Select from 'antd/lib/select'; +import Text from 'antd/lib/typography/Text'; +import { Model } from 'reducers/interfaces'; + +interface Props { + model: Model; +} + +export default function DeployedModelItem(props: Props): JSX.Element { + const { model } = props; + + return ( + + + {model.framework} + + + + {model.name} + + + + + {model.type} + + + + {model.description} + + + + + + ); +} diff --git a/cvat-ui/src/components/models-page/built-models-list.tsx b/cvat-ui/src/components/models-page/deployed-models-list.tsx similarity index 63% rename from cvat-ui/src/components/models-page/built-models-list.tsx rename to cvat-ui/src/components/models-page/deployed-models-list.tsx index a71d3e4a..93e301be 100644 --- a/cvat-ui/src/components/models-page/built-models-list.tsx +++ b/cvat-ui/src/components/models-page/deployed-models-list.tsx @@ -7,35 +7,38 @@ import { Row, Col } from 'antd/lib/grid'; import Text from 'antd/lib/typography/Text'; import { Model } from 'reducers/interfaces'; -import BuiltModelItemComponent from './built-model-item'; +import DeployedModelItem from './deployed-model-item'; + interface Props { models: Model[]; } -export default function IntegratedModelsListComponent(props: Props): JSX.Element { +export default function DeployedModelsListComponent(props: Props): JSX.Element { const { models } = props; + const items = models.map((model): JSX.Element => ( - + )); return ( <> - - - Primary - - - + Framework - + Name - + + Type + + + Description + + Labels diff --git a/cvat-ui/src/components/models-page/empty-list.tsx b/cvat-ui/src/components/models-page/empty-list.tsx index e850cff9..3eb9bde7 100644 --- a/cvat-ui/src/components/models-page/empty-list.tsx +++ b/cvat-ui/src/components/models-page/empty-list.tsx @@ -3,14 +3,12 @@ // SPDX-License-Identifier: MIT import React from 'react'; -import { Link } from 'react-router-dom'; import Text from 'antd/lib/typography/Text'; import { Row, Col } from 'antd/lib/grid'; import Icon from 'antd/lib/icon'; -import { - EmptyTasksIcon as EmptyModelsIcon, -} from 'icons'; +import consts from 'consts'; +import { EmptyTasksIcon as EmptyModelsIcon } from 'icons'; export default function EmptyListComponent(): JSX.Element { return ( @@ -22,7 +20,7 @@ export default function EmptyListComponent(): JSX.Element { - No models uploaded yet ... + No models deployed yet... @@ -32,7 +30,8 @@ export default function EmptyListComponent(): JSX.Element { - upload a new model + deploy a model with + nuclio
diff --git a/cvat-ui/src/components/models-page/models-page.tsx b/cvat-ui/src/components/models-page/models-page.tsx index 80c41eaf..a5606a2a 100644 --- a/cvat-ui/src/components/models-page/models-page.tsx +++ b/cvat-ui/src/components/models-page/models-page.tsx @@ -4,69 +4,29 @@ import './styles.scss'; import React from 'react'; -import Spin from 'antd/lib/spin'; import TopBarComponent from './top-bar'; -import UploadedModelsList from './uploaded-models-list'; -import BuiltModelsList from './built-models-list'; +import DeployedModelsList from './deployed-models-list'; import EmptyListComponent from './empty-list'; import FeedbackComponent from '../feedback/feedback'; import { Model } from '../../reducers/interfaces'; interface Props { - installedAutoAnnotation: boolean; - installedTFSegmentation: boolean; - installedTFAnnotation: boolean; - modelsInitialized: boolean; - modelsFetching: boolean; - registeredUsers: any[]; - models: Model[]; - getModels(): void; - deleteModel(id: number): void; + deployedModels: Model[]; } export default function ModelsPageComponent(props: Props): JSX.Element { - const { - installedAutoAnnotation, - installedTFSegmentation, - installedTFAnnotation, - modelsInitialized, - modelsFetching, - registeredUsers, - models, - - deleteModel, - } = props; - - if (!modelsInitialized) { - if (!modelsFetching) { - props.getModels(); - } - return ( - - ); - } - - const uploadedModels = models.filter((model): boolean => model.id !== null); - const integratedModels = models.filter((model): boolean => model.id === null); + const { deployedModels } = props; return (
- - { !!integratedModels.length - && } - { !!uploadedModels.length && ( - - )} - { installedAutoAnnotation - && !uploadedModels.length - && !installedTFAnnotation - && !installedTFSegmentation - && } + + { deployedModels.length + ? ( + + ) : ( + + )}
); diff --git a/cvat-ui/src/components/models-page/styles.scss b/cvat-ui/src/components/models-page/styles.scss index c67c893c..ddcaef8a 100644 --- a/cvat-ui/src/components/models-page/styles.scss +++ b/cvat-ui/src/components/models-page/styles.scss @@ -6,8 +6,10 @@ .cvat-models-page { padding-top: 30px; - height: 100%; + height: 90%; overflow: auto; + position: fixed; + width: 100%; > div:nth-child(1) { margin-bottom: 10px; @@ -52,7 +54,7 @@ .cvat-models-list-item { width: 100%; - height: 60px; + height: auto; border: 1px solid $border-color-1; border-radius: 3px; margin-bottom: 15px; diff --git a/cvat-ui/src/components/models-page/top-bar.tsx b/cvat-ui/src/components/models-page/top-bar.tsx index b58abd73..d0ede583 100644 --- a/cvat-ui/src/components/models-page/top-bar.tsx +++ b/cvat-ui/src/components/models-page/top-bar.tsx @@ -3,49 +3,15 @@ // SPDX-License-Identifier: MIT import React from 'react'; -import { RouteComponentProps } from 'react-router'; -import { withRouter } from 'react-router-dom'; import { Row, Col } from 'antd/lib/grid'; -import Button from 'antd/lib/button'; import Text from 'antd/lib/typography/Text'; -type Props = { - installedAutoAnnotation: boolean; -} & RouteComponentProps; - -function TopBarComponent(props: Props): JSX.Element { - const { - installedAutoAnnotation, - history, - } = props; - +export default function TopBarComponent(): JSX.Element { return ( - + Models - - { installedAutoAnnotation - && ( - - )} - ); } - -export default withRouter(TopBarComponent); diff --git a/cvat-ui/src/components/models-page/uploaded-model-item.tsx b/cvat-ui/src/components/models-page/uploaded-model-item.tsx deleted file mode 100644 index b108ec8d..00000000 --- a/cvat-ui/src/components/models-page/uploaded-model-item.tsx +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Tag from 'antd/lib/tag'; -import Select from 'antd/lib/select'; -import Icon from 'antd/lib/icon'; -import Menu from 'antd/lib/menu'; -import Dropdown from 'antd/lib/dropdown'; -import Text from 'antd/lib/typography/Text'; -import moment from 'moment'; - -import { MenuIcon } from 'icons'; -import { Model } from 'reducers/interfaces'; - -interface Props { - model: Model; - owner: any; - onDelete(): void; -} - -export default function UploadedModelItem(props: Props): JSX.Element { - const { - model, - owner, - onDelete, - } = props; - - return ( - - - OpenVINO - - - - {model.name} - - - - - {owner ? owner.username : 'undefined'} - - - - - {moment(model.uploadDate).format('MMMM Do YYYY')} - - - - - - - Actions - - { - onDelete(); - }} - key='delete' - > - Delete - - - ) - } - > - - - - - ); -} diff --git a/cvat-ui/src/components/models-page/uploaded-models-list.tsx b/cvat-ui/src/components/models-page/uploaded-models-list.tsx deleted file mode 100644 index be5a0354..00000000 --- a/cvat-ui/src/components/models-page/uploaded-models-list.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Text from 'antd/lib/typography/Text'; - -import { Model } from 'reducers/interfaces'; -import UploadedModelItem from './uploaded-model-item'; - - -interface Props { - registeredUsers: any[]; - models: Model[]; - deleteModel(id: number): void; -} - -export default function UploadedModelsListComponent(props: Props): JSX.Element { - const { - models, - registeredUsers, - deleteModel, - } = props; - - const items = models.map((model): JSX.Element => { - const owner = registeredUsers.filter((user) => user.id === model.ownerID)[0]; - return ( - deleteModel(model.id as number)} - /> - ); - }); - - return ( - <> - - - Uploaded by a user - - - - - - - Framework - - - Name - - - Owner - - - Uploaded - - - Labels - - - - { items } - - - - ); -} diff --git a/cvat-ui/src/components/register-page/register-form.tsx b/cvat-ui/src/components/register-page/register-form.tsx index daa38c8e..b1ed1107 100644 --- a/cvat-ui/src/components/register-page/register-form.tsx +++ b/cvat-ui/src/components/register-page/register-form.tsx @@ -11,7 +11,7 @@ import Checkbox from 'antd/lib/checkbox'; import patterns from 'utils/validation-patterns'; -import { UserAgreement } from 'reducers/interfaces' +import { UserAgreement } from 'reducers/interfaces'; import { Row, Col } from 'antd/lib/grid'; export interface UserConfirmation { @@ -31,7 +31,7 @@ export interface RegisterData { type RegisterFormProps = { fetching: boolean; - userAgreements: UserAgreement[], + userAgreements: UserAgreement[]; onSubmit(registerData: RegisterData): void; } & FormComponentProps; @@ -83,7 +83,7 @@ class RegisterFormComponent extends React.PureComponent { private validateAgrement = (agreement: any, value: any, callback: any): void => { const { userAgreements } = this.props; - let isValid: boolean = true; + let isValid = true; for (const userAgreement of userAgreements) { if (agreement.field === userAgreement.name && userAgreement.required && !value) { @@ -107,18 +107,20 @@ class RegisterFormComponent extends React.PureComponent { form.validateFields((error, values): void => { if (!error) { - values.confirmations = [] + const validatedFields = { + ...values, + confirmations: [], + }; for (const userAgreement of userAgreements) { - - values.confirmations.push({ + validatedFields.confirmations.push({ name: userAgreement.name, - value: values[userAgreement.name] + value: validatedFields[userAgreement.name], }); - delete values[userAgreement.name]; + delete validatedFields[userAgreement.name]; } - onSubmit(values); + onSubmit(validatedFields); } }); }; @@ -136,7 +138,7 @@ class RegisterFormComponent extends React.PureComponent { }], })( } + prefix={} placeholder='First name' />, )} @@ -157,7 +159,7 @@ class RegisterFormComponent extends React.PureComponent { }], })( } + prefix={} placeholder='Last name' />, )} @@ -179,7 +181,7 @@ class RegisterFormComponent extends React.PureComponent { }], })( } + prefix={} placeholder='Username' />, )} @@ -203,7 +205,7 @@ class RegisterFormComponent extends React.PureComponent { })( } + prefix={} placeholder='Email address' />, )} @@ -225,7 +227,7 @@ class RegisterFormComponent extends React.PureComponent { }], })(} + prefix={} placeholder='Password' />)} @@ -246,7 +248,7 @@ class RegisterFormComponent extends React.PureComponent { }], })(} + prefix={} placeholder='Confirm password' />)} @@ -255,8 +257,7 @@ class RegisterFormComponent extends React.PureComponent { private renderUserAgreements(): JSX.Element[] { const { form, userAgreements } = this.props; - const getUserAgreementsElements = () => - { + const getUserAgreementsElements = (): JSX.Element[] => { const agreementsList: JSX.Element[] = []; for (const userAgreement of userAgreements) { agreementsList.push( @@ -269,18 +270,24 @@ class RegisterFormComponent extends React.PureComponent { message: 'You must accept to continue!', }, { validator: this.validateAgrement, - }] + }], })( - I read and accept the { userAgreement.displayText } - + I read and accept the + + {` ${userAgreement.displayText}`} + + , )} - + , ); } return agreementsList; - } + }; return getUserAgreementsElements(); } diff --git a/cvat-ui/src/components/register-page/register-page.tsx b/cvat-ui/src/components/register-page/register-page.tsx index 1ab1d42a..72be3d6e 100644 --- a/cvat-ui/src/components/register-page/register-page.tsx +++ b/cvat-ui/src/components/register-page/register-page.tsx @@ -10,7 +10,7 @@ import Title from 'antd/lib/typography/Title'; import Text from 'antd/lib/typography/Text'; import { Row, Col } from 'antd/lib/grid'; -import { UserAgreement } from 'reducers/interfaces' +import { UserAgreement } from 'reducers/interfaces'; import CookieDrawer from 'components/login-page/cookie-policy-drawer'; import RegisterForm, { RegisterData, UserConfirmation } from './register-form'; diff --git a/cvat-ui/src/components/register-page/styles.scss b/cvat-ui/src/components/register-page/styles.scss index 3eaf5bea..12dd3c17 100644 --- a/cvat-ui/src/components/register-page/styles.scss +++ b/cvat-ui/src/components/register-page/styles.scss @@ -4,4 +4,4 @@ .ant-form-item { margin-bottom: 12px; -} \ No newline at end of file +} diff --git a/cvat-ui/src/components/settings-page/settings-page.tsx b/cvat-ui/src/components/settings-page/settings-page.tsx deleted file mode 100644 index 5347a063..00000000 --- a/cvat-ui/src/components/settings-page/settings-page.tsx +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import './styles.scss'; -import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Tabs from 'antd/lib/tabs'; -import Icon from 'antd/lib/icon'; -import Button from 'antd/lib/button'; -import Text from 'antd/lib/typography/Text'; - -import { RouteComponentProps } from 'react-router'; - -import WorkspaceSettingsContainer from 'containers/settings-page/workspace-settings'; -import PlayerSettingsContainer from 'containers/settings-page/player-settings'; - -function SettingsPage(props: RouteComponentProps): JSX.Element { - return ( -
- - - Settings - - - - - - - - Player - - ) - } - key='player' - > - - - - - Workspace - - ) - } - key='workspace' - > - - - - - - - - - - -
- ); -} - -export default SettingsPage; diff --git a/cvat-ui/src/components/task-page/details.tsx b/cvat-ui/src/components/task-page/details.tsx index 504c92ad..84c9aa1f 100644 --- a/cvat-ui/src/components/task-page/details.tsx +++ b/cvat-ui/src/components/task-page/details.tsx @@ -32,6 +32,7 @@ interface Props { interface State { name: string; bugTracker: string; + bugTrackerEditing: boolean; repository: string; repositoryStatus: string; } @@ -52,6 +53,7 @@ export default class DetailsComponent extends React.PureComponent this.state = { name: taskInstance.name, bugTracker: taskInstance.bugTracker, + bugTrackerEditing: false, repository: '', repositoryStatus: '', }; @@ -323,9 +325,14 @@ export default class DetailsComponent extends React.PureComponent taskInstance, onTaskUpdate, } = this.props; - const { bugTracker } = this.state; + const { bugTracker, bugTrackerEditing } = this.state; let shown = false; + const onStart = (): void => { + this.setState({ + bugTrackerEditing: true, + }); + }; const onChangeValue = (value: string): void => { if (value && !patterns.validateURL.pattern.test(value)) { if (!shown) { @@ -341,6 +348,7 @@ export default class DetailsComponent extends React.PureComponent } else { this.setState({ bugTracker: value, + bugTrackerEditing: false, }); taskInstance.bugTracker = value; @@ -377,7 +385,15 @@ export default class DetailsComponent extends React.PureComponent Issue Tracker
- Not specified + + {bugTrackerEditing ? '' : 'Not specified'} + ); diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 76d0a48d..dc89b51a 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -54,13 +54,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { > {`Job #${id}`} - | - - -
), }, { @@ -150,7 +143,7 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { Jobs - + - -{% endblock %} - -{% block note%} - {% autoescape off %} - {{ note }} - {% endautoescape %} -{% endblock %} \ No newline at end of file diff --git a/cvat/apps/authentication/templates/register.html b/cvat/apps/authentication/templates/register.html deleted file mode 100644 index b655e221..00000000 --- a/cvat/apps/authentication/templates/register.html +++ /dev/null @@ -1,32 +0,0 @@ - -{% extends "auth_base.html" %} - -{% block title %}Create user{% endblock %} - -{% block content %} - - -
- -
-{% endblock %} diff --git a/cvat/apps/authentication/templates/user_profile.html b/cvat/apps/authentication/templates/user_profile.html deleted file mode 100644 index 37ea7d49..00000000 --- a/cvat/apps/authentication/templates/user_profile.html +++ /dev/null @@ -1,21 +0,0 @@ - -
- {% if user.is_authenticated %} - {% if user.ldap_user and 'thumbnailPhoto' in user.ldap_user.attrs %} - {# TODO insert photo from ldap #} - {% endif %} - - {% if user.first_name and user.last_name %} -

{{ user.first_name }} {{ user.last_name }}

- {% else %} -

{{ user.username }}

- {% endif %} -
- -
- {% endif %} -
\ No newline at end of file diff --git a/cvat/apps/authentication/tests.py b/cvat/apps/authentication/tests.py deleted file mode 100644 index 53bc3b7a..00000000 --- a/cvat/apps/authentication/tests.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.test import TestCase - -# Create your tests here. - diff --git a/cvat/apps/authentication/urls.py b/cvat/apps/authentication/urls.py index e05d7340..d18ad09a 100644 --- a/cvat/apps/authentication/urls.py +++ b/cvat/apps/authentication/urls.py @@ -1,23 +1,38 @@ - # Copyright (C) 2018 Intel Corporation # # SPDX-License-Identifier: MIT -from django.urls import path -from django.contrib.auth import views as auth_views +from django.urls import path, re_path from django.conf import settings +from rest_auth.views import ( + LoginView, LogoutView, PasswordChangeView, + PasswordResetView, PasswordResetConfirmView) +from allauth.account.views import ConfirmEmailView, EmailVerificationSentView +from allauth.account import app_settings as allauth_settings -from . import forms -from . import views +from cvat.apps.authentication.views import SigningView, RegisterView urlpatterns = [ - path('login', auth_views.LoginView.as_view(form_class=forms.AuthForm, - template_name='login.html', extra_context={'note': settings.AUTH_LOGIN_NOTE}), - name='login'), - path('logout', auth_views.LogoutView.as_view(next_page='login'), name='logout'), + path('login', LoginView.as_view(), name='rest_login'), + path('logout', LogoutView.as_view(), name='rest_logout'), + path('signing', SigningView.as_view(), name='signing') ] if settings.DJANGO_AUTH_TYPE == 'BASIC': urlpatterns += [ - path('register', views.register_user, name='register'), + path('register', RegisterView.as_view(), name='rest_register'), + path('password/reset', PasswordResetView.as_view(), + name='rest_password_reset'), + path('password/reset/confirm', PasswordResetConfirmView.as_view(), + name='rest_password_reset_confirm'), + path('password/change', PasswordChangeView.as_view(), + name='rest_password_change'), ] + if allauth_settings.EMAIL_VERIFICATION != \ + allauth_settings.EmailVerificationMethod.NONE: + urlpatterns += [ + re_path(r'^account-confirm-email/(?P[-:\w]+)/$', ConfirmEmailView.as_view(), + name='account_confirm_email'), + path('register/account-email-verification-sent', EmailVerificationSentView.as_view(), + name='account_email_verification_sent'), + ] diff --git a/cvat/apps/authentication/views.py b/cvat/apps/authentication/views.py index b2497e2a..6f503199 100644 --- a/cvat/apps/authentication/views.py +++ b/cvat/apps/authentication/views.py @@ -2,35 +2,19 @@ # # SPDX-License-Identifier: MIT -from django.shortcuts import render, redirect -from django.conf import settings -from django.contrib.auth import login, authenticate from rest_framework import views from rest_framework.exceptions import ValidationError from rest_framework.response import Response +from rest_auth.registration.views import RegisterView as _RegisterView +from allauth.account import app_settings as allauth_settings from furl import furl -from . import forms from . import signature from django.utils.decorators import method_decorator from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi -def register_user(request): - if request.method == 'POST': - form = forms.NewUserForm(request.POST) - if form.is_valid(): - form.save() - username = form.cleaned_data.get('username') - raw_password = form.cleaned_data.get('password1') - user = authenticate(username=username, password=raw_password) - login(request, user) - return redirect(settings.LOGIN_REDIRECT_URL) - else: - form = forms.NewUserForm() - return render(request, 'register.html', {'form': form}) - @method_decorator(name='post', decorator=swagger_auto_schema( request_body=openapi.Schema( type=openapi.TYPE_OBJECT, @@ -61,3 +45,12 @@ class SigningView(views.APIView): url = furl(url).add({signature.QUERY_PARAM: sign}).url return Response(url) + + +class RegisterView(_RegisterView): + def get_response_data(self, user): + data = self.get_serializer(user).data + data['email_verification_required'] = allauth_settings.EMAIL_VERIFICATION == \ + allauth_settings.EmailVerificationMethod.MANDATORY + + return data diff --git a/cvat/apps/auto_annotation/README.md b/cvat/apps/auto_annotation/README.md deleted file mode 100644 index 27fecdf8..00000000 --- a/cvat/apps/auto_annotation/README.md +++ /dev/null @@ -1,372 +0,0 @@ -## Auto annotation - -- [Description](#description) -- [Installation](#installation) -- [Usage](#usage) -- [Testing script](#testing) -- [Examples](#examples) - - [Person-vehicle-bike-detection-crossroad-0078](#person-vehicle-bike-detection-crossroad-0078-openvino-toolkit) - - [Landmarks-regression-retail-0009](#landmarks-regression-retail-0009-openvino-toolkit) - - [Semantic Segmentation](#semantic-segmentation) -- [Available interpretation scripts](#available-interpretation-scripts) - -### Description - -The application will be enabled automatically if -[OpenVINO™ component](../../../components/openvino) -is installed. It allows to use custom models for auto annotation. Only models in -OpenVINO™ toolkit format are supported. If you would like to annotate a -task with a custom model please convert it to the intermediate representation -(IR) format via the model optimizer tool. See [OpenVINO documentation](https://software.intel.com/en-us/articles/OpenVINO-InferEngine) for details. - -### Installation - -See the installation instructions for [the OpenVINO component](../../../components/openvino) - -### Usage - -To annotate a task with a custom model you need to prepare 4 files: -1. __Model config__ (*.xml) - a text file with network configuration. -1. __Model weights__ (*.bin) - a binary file with trained weights. -1. __Label map__ (*.json) - a simple json file with `label_map` dictionary like -object with string values for label numbers. - Example: - ```json - { - "label_map": { - "0": "background", - "1": "aeroplane", - "2": "bicycle", - "3": "bird", - "4": "boat", - "5": "bottle", - "6": "bus", - "7": "car", - "8": "cat", - "9": "chair", - "10": "cow", - "11": "diningtable", - "12": "dog", - "13": "horse", - "14": "motorbike", - "15": "person", - "16": "pottedplant", - "17": "sheep", - "18": "sofa", - "19": "train", - "20": "tvmonitor" - } - } - ``` -1. __Interpretation script__ (*.py) - a file used to convert net output layer -to a predefined structure which can be processed by CVAT. This code will be run -inside a restricted python's environment, but it's possible to use some -builtin functions like __str, int, float, max, min, range__. - - Also two variables are available in the scope: - - - __detections__ - a list of dictionaries with detections for each frame: - * __frame_id__ - frame number - * __frame_height__ - frame height - * __frame_width__ - frame width - * __detections__ - output np.ndarray (See [ExecutableNetwork.infer](https://software.intel.com/en-us/articles/OpenVINO-InferEngine#inpage-nav-11-6-3) for details). - - - __results__ - an instance of python class with converted results. - Following methods should be used to add shapes: - ```python - # xtl, ytl, xbr, ybr - expected values are float or int - # label - expected value is int - # frame_number - expected value is int - # attributes - dictionary of attribute_name: attribute_value pairs, for example {"confidence": "0.83"} - add_box(self, xtl, ytl, xbr, ybr, label, frame_number, attributes=None) - - # points - list of (x, y) pairs of float or int, for example [(57.3, 100), (67, 102.7)] - # label - expected value is int - # frame_number - expected value is int - # attributes - dictionary of attribute_name: attribute_value pairs, for example {"confidence": "0.83"} - add_points(self, points, label, frame_number, attributes=None) - add_polygon(self, points, label, frame_number, attributes=None) - add_polyline(self, points, label, frame_number, attributes=None) - ``` - -### Testing script - -CVAT comes prepackaged with a small command line helper script to help develop interpretation scripts. - -It includes a small user interface which allows users to feed in images and see the results using -the user interfaces provided by OpenCV. - -See the script and the documentation in the -[auto_annotation directory](https://github.com/opencv/cvat/tree/develop/utils/auto_annotation). - -When using the Auto Annotation runner, it is often helpful to drop into a REPL prompt to interact with the variables -directly. You can do this using the `interact` method from the `code` module. - -```python -# Import the interact method from the `code` module -from code import interact - - -for frame_results in detections: - frame_height = frame_results["frame_height"] - frame_width = frame_results["frame_width"] - frame_number = frame_results["frame_id"] - # Unsure what other data members are in the `frame_results`? Use the `interact method! - interact(local=locals()) -``` - -```bash -$ python cvat/utils/auto_annotation/run_models.py --py /path/to/myfile.py --json /path/to/mapping.json --xml /path/to/inference.xml --bin /path/to/inference.bin -Python 3.6.6 (default, Sep 26 2018, 15:10:10) -[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.10.44.2)] on darwin -Type "help", "copyright", "credits" or "license" for more information. ->>> dir() -['__builtins__', 'frame_results', 'detections', 'frame_number', 'frame_height', 'interact', 'results', 'frame_width'] ->>> type(frame_results) - ->>> frame_results.keys() -dict_keys(['frame_id', 'frame_height', 'frame_width', 'detections']) -``` - -When using the `interact` method, make sure you are running using the _testing script_, and ensure that you _remove it_ - before submitting to the server! If you don't remove it from the server, the code runners will hang during execution, - and you'll have to restart the server to fix them. - -Another useful development method is visualizing the results using OpenCV. This will be discussed more in the -[Semantic Segmentation](#segmentation) section. - -### Examples - -#### [Person-vehicle-bike-detection-crossroad-0078](https://github.com/opencv/open_model_zoo/blob/2018/intel_models/person-vehicle-bike-detection-crossroad-0078/description/person-vehicle-bike-detection-crossroad-0078.md) (OpenVINO toolkit) - -__Links__ -- [person-vehicle-bike-detection-crossroad-0078.xml](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-vehicle-bike-detection-crossroad-0078/FP32/person-vehicle-bike-detection-crossroad-0078.xml) -- [person-vehicle-bike-detection-crossroad-0078.bin](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-vehicle-bike-detection-crossroad-0078/FP32/person-vehicle-bike-detection-crossroad-0078.bin) - -__Task labels__: person vehicle non-vehicle - -__label_map.json__: -```json -{ -"label_map": { - "1": "person", - "2": "vehicle", - "3": "non-vehicle" - } -} -``` -__Interpretation script for SSD based networks__: -```python -def clip(value): - return max(min(1.0, value), 0.0) - -for frame_results in detections: - frame_height = frame_results["frame_height"] - frame_width = frame_results["frame_width"] - frame_number = frame_results["frame_id"] - - for i in range(frame_results["detections"].shape[2]): - confidence = frame_results["detections"][0, 0, i, 2] - if confidence < 0.5: - continue - - results.add_box( - xtl=clip(frame_results["detections"][0, 0, i, 3]) * frame_width, - ytl=clip(frame_results["detections"][0, 0, i, 4]) * frame_height, - xbr=clip(frame_results["detections"][0, 0, i, 5]) * frame_width, - ybr=clip(frame_results["detections"][0, 0, i, 6]) * frame_height, - label=int(frame_results["detections"][0, 0, i, 1]), - frame_number=frame_number, - attributes={ - "confidence": "{:.2f}".format(confidence), - }, - ) -``` - -#### [Landmarks-regression-retail-0009](https://github.com/opencv/open_model_zoo/blob/2018/intel_models/landmarks-regression-retail-0009/description/landmarks-regression-retail-0009.md) (OpenVINO toolkit) - -__Links__ -- [landmarks-regression-retail-0009.xml](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/landmarks-regression-retail-0009/FP32/landmarks-regression-retail-0009.xml) -- [landmarks-regression-retail-0009.bin](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/landmarks-regression-retail-0009/FP32/landmarks-regression-retail-0009.bin) - -__Task labels__: left_eye right_eye tip_of_nose left_lip_corner right_lip_corner - -__label_map.json__: -```json -{ - "label_map": { - "0": "left_eye", - "1": "right_eye", - "2": "tip_of_nose", - "3": "left_lip_corner", - "4": "right_lip_corner" - } -} -``` -__Interpretation script__: -```python -def clip(value): - return max(min(1.0, value), 0.0) - -for frame_results in detections: - frame_height = frame_results["frame_height"] - frame_width = frame_results["frame_width"] - frame_number = frame_results["frame_id"] - - for i in range(0, frame_results["detections"].shape[1], 2): - x = frame_results["detections"][0, i, 0, 0] - y = frame_results["detections"][0, i + 1, 0, 0] - - results.add_points( - points=[(clip(x) * frame_width, clip(y) * frame_height)], - label=i // 2, # see label map and model output specification, - frame_number=frame_number, - ) -``` - -#### Semantic Segmentation - -__Links__ -- [masck_rcnn_resnet50_atrous_coco][1] (OpenvVINO toolkit) -- [CVAT Implemenation][2] - -__label_map.json__: -```json -{ -"label_map": { - "1": "person", - "2": "bicycle", - "3": "car", - } -} -``` - -Note that the above labels are not all the labels in the model! See [here](https://github.com/opencv/cvat/blob/develop/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json). - -**Interpretation script for a semantic segmentation network**: -```python -import numpy as np -import cv2 -from skimage.measure import approximate_polygon, find_contours - - -for frame_results in detections: - frame_height = frame_results['frame_height'] - frame_width = frame_results['frame_width'] - frame_number = frame_results['frame_id'] - detection = frame_results['detections'] - - # The keys for the below two members will vary based on the model - masks = frame_results['masks'] - boxes = frame_results['reshape_do_2d'] - - for box_index, box in enumerate(boxes): - # Again, these indexes specific to this model - class_label = int(box[1]) - box_class_probability = box[2] - - if box_class_probability > 0.2: - xmin = box[3] * frame_width - ymin = box[4] * frame_height - xmax = box[5] * frame_width - ymax = box[6] * frame_width - - box_width = int(xmax - xmin) - box_height = int(ymin - ymax) - - # use the box index and class label index to find the appropriate mask - # note that we need to convert the class label to a zero indexed array by subtracting `1` - class_mask = masks[box_index][class_label - 1] - - # Class mask is a 33 x 33 matrix - # resize it to the bounding box - resized_mask = cv2.resize(class_mask, dsize(box_height, box_width), interpolation=cv2.INTER_CUBIC) - - # Each pixel is a probability, select every pixel above the probability threshold, 0.5 - # Do this using the boolean `>` method - boolean_mask = (resized_mask > 0.5) - - # Convert the boolean values to uint8 - uint8_mask = boolean_mask.astype(np.uint8) * 255 - - # Change the x and y coordinates into integers - xmin = int(round(xmin)) - ymin = int(round(ymin)) - xmax = xmin + box_width - ymax = ymin + box_height - - # Create an empty blank frame, so that we can get the mask polygon in frame coordinates - mask_frame = np.zeros((frame_height, frame_width), dtype=np.uint8) - - # Put the uint8_mask on the mask frame using the integer coordinates - mask_frame[xmin:xmax, ymin:ymax] = uint8_mask - - mask_probability_threshold = 0.5 - # find the contours - contours = find_contours(mask_frame, mask_probability_threshold) - # every bounding box should only have a single contour - contour = contours[0] - contour = np.flip(contour, axis=1) - - # reduce the precision on the polygon - polygon_mask = approximate_polygon(contour, tolerance=2.5) - polygon_mask = polygon_mask.tolist() - - results.add_polygon(polygon_mask, class_label, frame_number) -``` - -Note that it is sometimes hard to see or understand what is happening in a script. -Use of the computer vision module can help you visualize what is happening. - -```python -import cv2 - - -for frame_results in detections: - frame_height = frame_results['frame_height'] - frame_width = frame_results['frame_width'] - detection = frame_results['detections'] - - masks = frame_results['masks'] - boxes = frame_results['reshape_do_2d'] - - for box_index, box in enumerate(boxes): - class_label = int(box[1]) - box_class_probability = box[2] - - if box_class_probability > 0.2: - xmin = box[3] * frame_width - ymin = box[4] * frame_height - xmax = box[5] * frame_width - ymax = box[6] * frame_width - - box_width = int(xmax - xmin) - box_height = int(ymin - ymax) - - class_mask = masks[box_index][class_label - 1] - # Visualize the class mask! - cv2.imshow('class mask', class_mask) - # wait until user presses keys - cv2.waitKeys() - - boolean_mask = (resized_mask > 0.5) - uint8_mask = boolean_mask.astype(np.uint8) * 255 - - # Visualize the class mask after it's been resized! - cv2.imshow('class mask', uint8_mask) - cv2.waitKeys() -``` - -Note that you should _only_ use the above commands while running the [Auto Annotation Model Runner][3]. -Running on the server will likely require a server restart to fix. -The method `cv2.destroyAllWindows()` or `cv2.destroyWindow('your-name-here')` might be required depending on your - implementation. - -### Available interpretation scripts - -CVAT comes prepackaged with several out of the box interpretation scripts. -See them in the [open model zoo directory](https://github.com/opencv/cvat/tree/develop/utils/open_model_zoo) - -[1]: https://github.com/opencv/open_model_zoo/blob/master/models/public/mask_rcnn_resnet50_atrous_coco/model.yml -[2]: https://github.com/opencv/cvat/tree/develop/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco -[3]: https://github.com/opencv/cvat/tree/develop/utils/auto_annotation diff --git a/cvat/apps/auto_annotation/__init__.py b/cvat/apps/auto_annotation/__init__.py deleted file mode 100644 index c929093f..00000000 --- a/cvat/apps/auto_annotation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT - -default_app_config = 'cvat.apps.auto_annotation.apps.AutoAnnotationConfig' diff --git a/cvat/apps/auto_annotation/admin.py b/cvat/apps/auto_annotation/admin.py deleted file mode 100644 index da1eabb4..00000000 --- a/cvat/apps/auto_annotation/admin.py +++ /dev/null @@ -1,15 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.contrib import admin -from .models import AnnotationModel - -@admin.register(AnnotationModel) -class AnnotationModelAdmin(admin.ModelAdmin): - list_display = ('name', 'owner', 'created_date', 'updated_date', - 'shared', 'primary', 'framework') - - def has_add_permission(self, request): - return False diff --git a/cvat/apps/auto_annotation/apps.py b/cvat/apps/auto_annotation/apps.py deleted file mode 100644 index cea75abf..00000000 --- a/cvat/apps/auto_annotation/apps.py +++ /dev/null @@ -1,15 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - - -class AutoAnnotationConfig(AppConfig): - name = "cvat.apps.auto_annotation" - - def ready(self): - from .permissions import setup_permissions - - setup_permissions() diff --git a/cvat/apps/auto_annotation/image_loader.py b/cvat/apps/auto_annotation/image_loader.py deleted file mode 100644 index 17335beb..00000000 --- a/cvat/apps/auto_annotation/image_loader.py +++ /dev/null @@ -1,22 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import cv2 -import numpy as np - -class ImageLoader(): - def __init__(self, frame_provider): - self._frame_provider = frame_provider - - def __iter__(self): - for frame, _ in self._frame_provider.get_frames(self._frame_provider.Quality.ORIGINAL): - yield self._load_image(frame) - - def __len__(self): - return len(self._frame_provider) - - @staticmethod - def _load_image(image): - return cv2.imdecode(np.fromstring(image.read(), np.uint8), cv2.IMREAD_COLOR) diff --git a/cvat/apps/auto_annotation/inference.py b/cvat/apps/auto_annotation/inference.py deleted file mode 100644 index b51cc10e..00000000 --- a/cvat/apps/auto_annotation/inference.py +++ /dev/null @@ -1,163 +0,0 @@ -import itertools -from .model_loader import ModelLoader -from cvat.apps.engine.utils import import_modules, execute_python_code - -def _process_detections(detections, path_to_conv_script, restricted=True): - results = Results() - local_vars = { - "detections": detections, - "results": results, - } - source_code = open(path_to_conv_script).read() - - if restricted: - global_vars = { - "__builtins__": { - "str": str, - "int": int, - "float": float, - "max": max, - "min": min, - "range": range, - }, - } - else: - global_vars = globals() - imports = import_modules(source_code) - global_vars.update(imports) - - - execute_python_code(source_code, global_vars, local_vars) - - return results - -def _process_attributes(shape_attributes, label_attr_spec): - attributes = [] - for attr_text, attr_value in shape_attributes.items(): - if attr_text in label_attr_spec: - attributes.append({ - "spec_id": label_attr_spec[attr_text], - "value": attr_value, - }) - - return attributes - -class Results(): - def __init__(self): - self._results = { - "shapes": [], - "tracks": [] - } - - # https://stackoverflow.com/a/50928627/2701402 - def add_box(self, xtl: float, ytl: float, xbr: float, ybr: float, label: int, frame_number: int, attributes: dict=None): - """ - xtl - x coordinate, top left - ytl - y coordinate, top left - xbr - x coordinate, bottom right - ybr - y coordinate, bottom right - """ - self.get_shapes().append({ - "label": label, - "frame": frame_number, - "points": [xtl, ytl, xbr, ybr], - "type": "rectangle", - "attributes": attributes or {}, - }) - - def add_points(self, points: list, label: int, frame_number: int, attributes: dict=None): - points = self._create_polyshape(points, label, frame_number, attributes) - points["type"] = "points" - self.get_shapes().append(points) - - def add_polygon(self, points: list, label: int, frame_number: int, attributes: dict=None): - polygon = self._create_polyshape(points, label, frame_number, attributes) - polygon["type"] = "polygon" - self.get_shapes().append(polygon) - - def add_polyline(self, points: list, label: int, frame_number: int, attributes: dict=None): - polyline = self._create_polyshape(points, label, frame_number, attributes) - polyline["type"] = "polyline" - self.get_shapes().append(polyline) - - def get_shapes(self): - return self._results["shapes"] - - def get_tracks(self): - return self._results["tracks"] - - @staticmethod - def _create_polyshape(points: list, label: int, frame_number: int, attributes: dict=None): - return { - "label": label, - "frame": frame_number, - "points": list(itertools.chain.from_iterable(points)), - "attributes": attributes or {}, - } - -class InferenceAnnotationRunner: - def __init__(self, data, model_file, weights_file, labels_mapping, - attribute_spec, convertation_file): - self.data = iter(data) - self.data_len = len(data) - self.model = ModelLoader(model=model_file, weights=weights_file) - self.frame_counter = 0 - self.attribute_spec = attribute_spec - self.convertation_file = convertation_file - self.iteration_size = 128 - self.labels_mapping = labels_mapping - - - def run(self, job=None, update_progress=None, restricted=True): - result = { - "shapes": [], - "tracks": [], - "tags": [], - "version": 0 - } - - detections = [] - for _ in range(self.iteration_size): - try: - frame = next(self.data) - except StopIteration: - break - - orig_rows, orig_cols = frame.shape[:2] - - detections.append({ - "frame_id": self.frame_counter, - "frame_height": orig_rows, - "frame_width": orig_cols, - "detections": self.model.infer(frame), - }) - - self.frame_counter += 1 - if job and update_progress and not update_progress(job, self.frame_counter * 100 / self.data_len): - return None, False - - processed_detections = _process_detections(detections, self.convertation_file, restricted=restricted) - - self._add_shapes(processed_detections.get_shapes(), result["shapes"]) - - more_items = self.frame_counter != self.data_len - - return result, more_items - - def _add_shapes(self, shapes, target_container): - for shape in shapes: - if shape["label"] not in self.labels_mapping: - continue - - db_label = self.labels_mapping[shape["label"]] - label_attr_spec = self.attribute_spec.get(db_label) - target_container.append({ - "label_id": db_label, - "frame": shape["frame"], - "points": shape["points"], - "type": shape["type"], - "z_order": 0, - "group": None, - "occluded": False, - "attributes": _process_attributes(shape["attributes"], label_attr_spec), - }) diff --git a/cvat/apps/auto_annotation/inference_engine.py b/cvat/apps/auto_annotation/inference_engine.py deleted file mode 100644 index 766c0eb0..00000000 --- a/cvat/apps/auto_annotation/inference_engine.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version - -import subprocess -import os -import platform - -_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) - -def _check_instruction(instruction): - return instruction == str.strip( - subprocess.check_output( - 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True - ).decode('utf-8') - ) - - -def make_plugin_or_core(): - version = get_version() - use_core_openvino = False - try: - major, minor, reference = [int(x) for x in version.split('.')] - if major >= 2 and minor >= 1: - use_core_openvino = True - except Exception: - pass - - if use_core_openvino: - ie = IECore() - return ie - - if _IE_PLUGINS_PATH is None: - raise OSError('Inference engine plugin path env not found in the system.') - - plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) - if (_check_instruction('avx2')): - plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) - elif (_check_instruction('sse4')): - plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) - elif platform.system() == 'Darwin': - plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) - else: - raise Exception('Inference engine requires a support of avx2 or sse4.') - - return plugin - - -def make_network(model, weights): - return IENetwork(model = model, weights = weights) diff --git a/cvat/apps/auto_annotation/migrations/0001_initial.py b/cvat/apps/auto_annotation/migrations/0001_initial.py deleted file mode 100644 index ebd8a6e1..00000000 --- a/cvat/apps/auto_annotation/migrations/0001_initial.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 2.1.3 on 2019-01-24 14:05 - -import cvat.apps.auto_annotation.models -from django.conf import settings -import django.core.files.storage -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='AnnotationModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', cvat.apps.auto_annotation.models.SafeCharField(max_length=256)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now_add=True)), - ('model_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('weights_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('labelmap_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('interpretation_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('shared', models.BooleanField(default=False)), - ('primary', models.BooleanField(default=False)), - ('framework', models.CharField(default=cvat.apps.auto_annotation.models.FrameworkChoice('openvino'), max_length=32)), - ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'default_permissions': (), - }, - ), - ] diff --git a/cvat/apps/auto_annotation/migrations/__init__.py b/cvat/apps/auto_annotation/migrations/__init__.py deleted file mode 100644 index d8e62e54..00000000 --- a/cvat/apps/auto_annotation/migrations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - diff --git a/cvat/apps/auto_annotation/model_loader.py b/cvat/apps/auto_annotation/model_loader.py deleted file mode 100644 index e48d5c8e..00000000 --- a/cvat/apps/auto_annotation/model_loader.py +++ /dev/null @@ -1,76 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import json -import cv2 -import os -import numpy as np - -from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network - -class ModelLoader(): - def __init__(self, model, weights): - self._model = model - self._weights = weights - - core_or_plugin = make_plugin_or_core() - network = make_network(self._model, self._weights) - - if getattr(core_or_plugin, 'get_supported_layers', False): - supported_layers = core_or_plugin.get_supported_layers(network) - not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] - if len(not_supported_layers) != 0: - raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". - format(core_or_plugin.device, ", ".join(not_supported_layers))) - - iter_inputs = iter(network.inputs) - self._input_blob_name = next(iter_inputs) - self._input_info_name = '' - self._output_blob_name = next(iter(network.outputs)) - - self._require_image_info = False - - info_names = ('image_info', 'im_info') - - # NOTE: handeling for the inclusion of `image_info` in OpenVino2019 - if any(s in network.inputs for s in info_names): - self._require_image_info = True - self._input_info_name = set(network.inputs).intersection(info_names) - self._input_info_name = self._input_info_name.pop() - if self._input_blob_name in info_names: - self._input_blob_name = next(iter_inputs) - - if getattr(core_or_plugin, 'load_network', False): - self._net = core_or_plugin.load_network(network, - "CPU", - num_requests=2) - else: - self._net = core_or_plugin.load(network=network, num_requests=2) - input_type = network.inputs[self._input_blob_name] - self._input_layout = input_type if isinstance(input_type, list) else input_type.shape - - def infer(self, image): - _, _, h, w = self._input_layout - in_frame = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h)) - in_frame = in_frame.transpose((2, 0, 1)) # Change data layout from HWC to CHW - inputs = {self._input_blob_name: in_frame} - if self._require_image_info: - info = np.zeros([1, 3]) - info[0, 0] = h - info[0, 1] = w - # frame number - info[0, 2] = 1 - inputs[self._input_info_name] = info - - results = self._net.infer(inputs) - if len(results) == 1: - return results[self._output_blob_name].copy() - else: - return results.copy() - - -def load_labelmap(labels_path): - with open(labels_path, "r") as f: - return json.load(f)["label_map"] diff --git a/cvat/apps/auto_annotation/model_manager.py b/cvat/apps/auto_annotation/model_manager.py deleted file mode 100644 index 37f6cc05..00000000 --- a/cvat/apps/auto_annotation/model_manager.py +++ /dev/null @@ -1,263 +0,0 @@ -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import django_rq -import numpy as np -import os -import rq -import shutil -import tempfile - -from django.db import transaction -from django.utils import timezone -from django.conf import settings - -from cvat.apps.engine.log import slogger -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.authentication.auth import has_admin_role -from cvat.apps.engine.serializers import LabeledDataSerializer -from cvat.apps.dataset_manager.task import put_task_data, patch_task_data -from cvat.apps.engine.frame_provider import FrameProvider - -from .models import AnnotationModel, FrameworkChoice -from .model_loader import load_labelmap -from .image_loader import ImageLoader -from .inference import InferenceAnnotationRunner - - -def _remove_old_file(model_file_field): - if model_file_field and os.path.exists(model_file_field.name): - os.remove(model_file_field.name) - -def _update_dl_model_thread(dl_model_id, name, is_shared, model_file, weights_file, labelmap_file, - interpretation_file, run_tests, is_local_storage, delete_if_test_fails, restricted=True): - def _get_file_content(filename): - return os.path.basename(filename), open(filename, "rb") - - def _delete_source_files(): - for f in [model_file, weights_file, labelmap_file, interpretation_file]: - if f: - os.remove(f) - - def _run_test(model_file, weights_file, labelmap_file, interpretation_file): - test_image = np.ones((1024, 1980, 3), np.uint8) * 255 - try: - dummy_labelmap = {key: key for key in load_labelmap(labelmap_file).keys()} - runner = InferenceAnnotationRunner( - data=[test_image,], - model_file=model_file, - weights_file=weights_file, - labels_mapping=dummy_labelmap, - attribute_spec={}, - convertation_file=interpretation_file) - - runner.run(restricted=restricted) - except Exception as e: - return False, str(e) - - return True, "" - - job = rq.get_current_job() - job.meta["progress"] = "Saving data" - job.save_meta() - - with transaction.atomic(): - dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id) - - test_res = True - message = "" - if run_tests: - job.meta["progress"] = "Test started" - job.save_meta() - - test_res, message = _run_test( - model_file=model_file or dl_model.model_file.name, - weights_file=weights_file or dl_model.weights_file.name, - labelmap_file=labelmap_file or dl_model.labelmap_file.name, - interpretation_file=interpretation_file or dl_model.interpretation_file.name, - ) - - if not test_res: - job.meta["progress"] = "Test failed" - if delete_if_test_fails: - shutil.rmtree(dl_model.get_dirname(), ignore_errors=True) - dl_model.delete() - else: - job.meta["progress"] = "Test passed" - job.save_meta() - - # update DL model - if test_res: - if model_file: - _remove_old_file(dl_model.model_file) - dl_model.model_file.save(*_get_file_content(model_file)) - if weights_file: - _remove_old_file(dl_model.weights_file) - dl_model.weights_file.save(*_get_file_content(weights_file)) - if labelmap_file: - _remove_old_file(dl_model.labelmap_file) - dl_model.labelmap_file.save(*_get_file_content(labelmap_file)) - if interpretation_file: - _remove_old_file(dl_model.interpretation_file) - dl_model.interpretation_file.save(*_get_file_content(interpretation_file)) - - if name: - dl_model.name = name - - if is_shared != None: - dl_model.shared = is_shared - - dl_model.updated_date = timezone.now() - dl_model.save() - - if is_local_storage: - _delete_source_files() - - if not test_res: - raise Exception("Model was not properly created/updated. Test failed: {}".format(message)) - -def create_or_update(dl_model_id, name, model_file, weights_file, labelmap_file, interpretation_file, owner, storage, is_shared): - def get_abs_path(share_path): - if not share_path: - return share_path - share_root = settings.SHARE_ROOT - relpath = os.path.normpath(share_path).lstrip('/') - if '..' in relpath.split(os.path.sep): - raise Exception('Permission denied') - abspath = os.path.abspath(os.path.join(share_root, relpath)) - if os.path.commonprefix([share_root, abspath]) != share_root: - raise Exception('Bad file path on share: ' + abspath) - return abspath - - def save_file_as_tmp(data): - if not data: - return None - fd, filename = tempfile.mkstemp() - with open(filename, 'wb') as tmp_file: - for chunk in data.chunks(): - tmp_file.write(chunk) - os.close(fd) - return filename - is_create_request = dl_model_id is None - if is_create_request: - dl_model_id = create_empty(owner=owner) - - run_tests = bool(model_file or weights_file or labelmap_file or interpretation_file) - if storage != "local": - model_file = get_abs_path(model_file) - weights_file = get_abs_path(weights_file) - labelmap_file = get_abs_path(labelmap_file) - interpretation_file = get_abs_path(interpretation_file) - else: - model_file = save_file_as_tmp(model_file) - weights_file = save_file_as_tmp(weights_file) - labelmap_file = save_file_as_tmp(labelmap_file) - interpretation_file = save_file_as_tmp(interpretation_file) - - if owner: - restricted = not has_admin_role(owner) - else: - restricted = not has_admin_role(AnnotationModel.objects.get(pk=dl_model_id).owner) - - rq_id = "auto_annotation.create.{}".format(dl_model_id) - queue = django_rq.get_queue("default") - queue.enqueue_call( - func=_update_dl_model_thread, - args=( - dl_model_id, - name, - is_shared, - model_file, - weights_file, - labelmap_file, - interpretation_file, - run_tests, - storage == "local", - is_create_request, - restricted - ), - job_id=rq_id - ) - - return rq_id - -@transaction.atomic -def create_empty(owner, framework=FrameworkChoice.OPENVINO): - db_model = AnnotationModel( - owner=owner, - ) - db_model.save() - - model_path = db_model.get_dirname() - if os.path.isdir(model_path): - shutil.rmtree(model_path) - os.mkdir(model_path) - - return db_model.id - -@transaction.atomic -def delete(dl_model_id): - dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id) - if dl_model: - if dl_model.primary: - raise Exception("Can not delete primary model {}".format(dl_model_id)) - - shutil.rmtree(dl_model.get_dirname(), ignore_errors=True) - dl_model.delete() - else: - raise Exception("Requested DL model {} doesn't exist".format(dl_model_id)) - -def run_inference_thread(tid, model_file, weights_file, labels_mapping, attributes, convertation_file, reset, user, restricted=True): - def update_progress(job, progress): - job.refresh() - if "cancel" in job.meta: - del job.meta["cancel"] - job.save() - return False - job.meta["progress"] = progress - job.save_meta() - return True - - try: - job = rq.get_current_job() - job.meta["progress"] = 0 - job.save_meta() - db_task = TaskModel.objects.get(pk=tid) - - result = None - slogger.glob.info("auto annotation with openvino toolkit for task {}".format(tid)) - more_data = True - runner = InferenceAnnotationRunner( - data=ImageLoader(FrameProvider(db_task.data)), - model_file=model_file, - weights_file=weights_file, - labels_mapping=labels_mapping, - attribute_spec=attributes, - convertation_file= convertation_file) - while more_data: - result, more_data = runner.run( - job=job, - update_progress=update_progress, - restricted=restricted) - - if result is None: - slogger.glob.info("auto annotation for task {} canceled by user".format(tid)) - return - - serializer = LabeledDataSerializer(data = result) - if serializer.is_valid(raise_exception=True): - if reset: - put_task_data(tid, result) - else: - patch_task_data(tid, result, "create") - - slogger.glob.info("auto annotation for task {} done".format(tid)) - except Exception as e: - try: - slogger.task[tid].exception("exception was occurred during auto annotation of the task", exc_info=True) - except Exception as ex: - slogger.glob.exception("exception was occurred during auto annotation of the task {}: {}".format(tid, str(ex)), exc_info=True) - raise ex - - raise e diff --git a/cvat/apps/auto_annotation/models.py b/cvat/apps/auto_annotation/models.py deleted file mode 100644 index 467997e0..00000000 --- a/cvat/apps/auto_annotation/models.py +++ /dev/null @@ -1,56 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import os -from enum import Enum - -from django.db import models -from django.conf import settings -from django.contrib.auth.models import User -from django.core.files.storage import FileSystemStorage - -fs = FileSystemStorage() - -def upload_path_handler(instance, filename): - return os.path.join(settings.MODELS_ROOT, str(instance.id), filename) - -class FrameworkChoice(Enum): - OPENVINO = 'openvino' - TENSORFLOW = 'tensorflow' - PYTORCH = 'pytorch' - - def __str__(self): - return self.value - - -class SafeCharField(models.CharField): - def get_prep_value(self, value): - value = super().get_prep_value(value) - if value: - return value[:self.max_length] - return value - -class AnnotationModel(models.Model): - name = SafeCharField(max_length=256) - owner = models.ForeignKey(User, null=True, blank=True, - on_delete=models.SET_NULL) - created_date = models.DateTimeField(auto_now_add=True) - updated_date = models.DateTimeField(auto_now_add=True) - model_file = models.FileField(upload_to=upload_path_handler, storage=fs) - weights_file = models.FileField(upload_to=upload_path_handler, storage=fs) - labelmap_file = models.FileField(upload_to=upload_path_handler, storage=fs) - interpretation_file = models.FileField(upload_to=upload_path_handler, storage=fs) - shared = models.BooleanField(default=False) - primary = models.BooleanField(default=False) - framework = models.CharField(max_length=32, default=FrameworkChoice.OPENVINO) - - class Meta: - default_permissions = () - - def get_dirname(self): - return "{models_root}/{id}".format(models_root=settings.MODELS_ROOT, id=self.id) - - def __str__(self): - return self.name diff --git a/cvat/apps/auto_annotation/permissions.py b/cvat/apps/auto_annotation/permissions.py deleted file mode 100644 index ede8d611..00000000 --- a/cvat/apps/auto_annotation/permissions.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import rules - -from cvat.apps.authentication.auth import has_admin_role, has_user_role - -@rules.predicate -def is_model_owner(db_user, db_dl_model): - return db_dl_model.owner == db_user - -@rules.predicate -def is_shared_model(_, db_dl_model): - return db_dl_model.shared - -@rules.predicate -def is_primary_model(_, db_dl_model): - return db_dl_model.primary - -def setup_permissions(): - rules.add_perm('auto_annotation.model.create', has_admin_role | has_user_role) - - rules.add_perm('auto_annotation.model.update', (has_admin_role | is_model_owner) & ~is_primary_model) - - rules.add_perm('auto_annotation.model.delete', (has_admin_role | is_model_owner) & ~is_primary_model) - - rules.add_perm('auto_annotation.model.access', has_admin_role | is_model_owner | - is_shared_model | is_primary_model) diff --git a/cvat/apps/auto_annotation/tests.py b/cvat/apps/auto_annotation/tests.py deleted file mode 100644 index a59acdef..00000000 --- a/cvat/apps/auto_annotation/tests.py +++ /dev/null @@ -1,4 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/auto_annotation/urls.py b/cvat/apps/auto_annotation/urls.py deleted file mode 100644 index 2aa75c5e..00000000 --- a/cvat/apps/auto_annotation/urls.py +++ /dev/null @@ -1,19 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path("create", views.create_model), - path("update/", views.update_model), - path("delete/", views.delete_model), - - path("start//", views.start_annotation), - path("check/", views.check), - path("cancel/", views.cancel), - - path("meta/get", views.get_meta_info), -] diff --git a/cvat/apps/auto_annotation/views.py b/cvat/apps/auto_annotation/views.py deleted file mode 100644 index c521424d..00000000 --- a/cvat/apps/auto_annotation/views.py +++ /dev/null @@ -1,265 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import django_rq -import json -import os - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from rest_framework.decorators import api_view -from django.db.models import Q -from rules.contrib.views import permission_required, objectgetter - -from cvat.apps.authentication.decorators import login_required -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.authentication.auth import has_admin_role -from cvat.apps.engine.log import slogger - -from .model_loader import load_labelmap -from . import model_manager -from .models import AnnotationModel - -@login_required -@permission_required(perm=["engine.task.change"], - fn=objectgetter(TaskModel, "tid"), raise_exception=True) -def cancel(request, tid): - try: - queue = django_rq.get_queue("low") - job = queue.fetch_job("auto_annotation.run.{}".format(tid)) - if job is None or job.is_finished or job.is_failed: - raise Exception("Task is not being annotated currently") - elif "cancel" not in job.meta: - job.meta["cancel"] = True - job.save() - - except Exception as ex: - try: - slogger.task[tid].exception("cannot cancel auto annotation for task #{}".format(tid), exc_info=True) - except Exception as logger_ex: - slogger.glob.exception("exception was occured during cancel auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() - -@login_required -@permission_required(perm=["auto_annotation.model.create"], raise_exception=True) -def create_model(request): - if request.method != 'POST': - return HttpResponseBadRequest("Only POST requests are accepted") - - try: - params = request.POST - storage = params["storage"] - name = params["name"] - is_shared = params["shared"].lower() == "true" - if is_shared and not has_admin_role(request.user): - raise Exception("Only admin can create shared models") - - files = request.FILES if storage == "local" else params - model = files["xml"] - weights = files["bin"] - labelmap = files["json"] - interpretation_script = files["py"] - owner = request.user - - rq_id = model_manager.create_or_update( - dl_model_id=None, - name=name, - model_file=model, - weights_file=weights, - labelmap_file=labelmap, - interpretation_file=interpretation_script, - owner=owner, - storage=storage, - is_shared=is_shared, - ) - - return JsonResponse({"id": rq_id}) - except Exception as e: - return HttpResponseBadRequest(str(e)) - -@login_required -@permission_required(perm=["auto_annotation.model.update"], - fn=objectgetter(AnnotationModel, "mid"), raise_exception=True) -def update_model(request, mid): - if request.method != 'POST': - return HttpResponseBadRequest("Only POST requests are accepted") - - try: - params = request.POST - storage = params["storage"] - name = params.get("name") - is_shared = params.get("shared") - is_shared = is_shared.lower() == "true" if is_shared else None - if is_shared and not has_admin_role(request.user): - raise Exception("Only admin can create shared models") - files = request.FILES - model = files.get("xml") - weights = files.get("bin") - labelmap = files.get("json") - interpretation_script = files.get("py") - - rq_id = model_manager.create_or_update( - dl_model_id=mid, - name=name, - model_file=model, - weights_file=weights, - labelmap_file=labelmap, - interpretation_file=interpretation_script, - owner=None, - storage=storage, - is_shared=is_shared, - ) - - return JsonResponse({"id": rq_id}) - except Exception as e: - return HttpResponseBadRequest(str(e)) - -@login_required -@permission_required(perm=["auto_annotation.model.delete"], - fn=objectgetter(AnnotationModel, "mid"), raise_exception=True) -def delete_model(request, mid): - if request.method != 'DELETE': - return HttpResponseBadRequest("Only DELETE requests are accepted") - model_manager.delete(mid) - return HttpResponse() - -@api_view(['POST']) -@login_required -def get_meta_info(request): - try: - tids = request.data - response = { - "admin": has_admin_role(request.user), - "models": [], - "run": {}, - } - dl_model_list = list(AnnotationModel.objects.filter(Q(owner=request.user) | Q(primary=True) | Q(shared=True)).order_by('-created_date')) - for dl_model in dl_model_list: - labels = [] - if dl_model.labelmap_file and os.path.exists(dl_model.labelmap_file.name): - with dl_model.labelmap_file.open('r') as f: - labels = list(json.load(f)["label_map"].values()) - - response["models"].append({ - "id": dl_model.id, - "name": dl_model.name, - "primary": dl_model.primary, - "uploadDate": dl_model.created_date, - "updateDate": dl_model.updated_date, - "labels": labels, - "owner": dl_model.owner.id, - }) - - queue = django_rq.get_queue("low") - for tid in tids: - rq_id = "auto_annotation.run.{}".format(tid) - job = queue.fetch_job(rq_id) - if job is not None: - response["run"][tid] = { - "status": job.get_status(), - "rq_id": rq_id, - } - - return JsonResponse(response) - except Exception as e: - return HttpResponseBadRequest(str(e)) - -@login_required -@permission_required(perm=["engine.task.change"], - fn=objectgetter(TaskModel, "tid"), raise_exception=True) -@permission_required(perm=["auto_annotation.model.access"], - fn=objectgetter(AnnotationModel, "mid"), raise_exception=True) -def start_annotation(request, mid, tid): - slogger.glob.info("auto annotation create request for task {} via DL model {}".format(tid, mid)) - try: - db_task = TaskModel.objects.get(pk=tid) - queue = django_rq.get_queue("low") - job = queue.fetch_job("auto_annotation.run.{}".format(tid)) - if job is not None and (job.is_started or job.is_queued): - raise Exception("The process is already running") - - data = json.loads(request.body.decode('utf-8')) - - should_reset = data["reset"] - user_defined_labels_mapping = data["labels"] - - dl_model = AnnotationModel.objects.get(pk=mid) - - model_file_path = dl_model.model_file.name - weights_file_path = dl_model.weights_file.name - labelmap_file = dl_model.labelmap_file.name - convertation_file_path = dl_model.interpretation_file.name - restricted = not has_admin_role(dl_model.owner) - - db_labels = db_task.label_set.prefetch_related("attributespec_set").all() - db_attributes = {db_label.id: - {db_attr.name: db_attr.id for db_attr in db_label.attributespec_set.all()} for db_label in db_labels} - db_labels = {db_label.name:db_label.id for db_label in db_labels} - - model_labels = {value: key for key, value in load_labelmap(labelmap_file).items()} - - labels_mapping = {} - for user_model_label, user_db_label in user_defined_labels_mapping.items(): - if user_model_label in model_labels and user_db_label in db_labels: - labels_mapping[int(model_labels[user_model_label])] = db_labels[user_db_label] - - if not labels_mapping: - raise Exception("No labels found for annotation") - - rq_id="auto_annotation.run.{}".format(tid) - queue.enqueue_call(func=model_manager.run_inference_thread, - args=( - tid, - model_file_path, - weights_file_path, - labels_mapping, - db_attributes, - convertation_file_path, - should_reset, - request.user, - restricted, - ), - job_id = rq_id, - timeout=604800) # 7 days - - slogger.task[tid].info("auto annotation job enqueued") - - except Exception as ex: - try: - slogger.task[tid].exception("exception was occurred during annotation request", exc_info=True) - except Exception as logger_ex: - slogger.glob.exception("exception was occurred during create auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - return JsonResponse({"id": rq_id}) - -@login_required -def check(request, rq_id): - try: - target_queue = "low" if "auto_annotation.run" in rq_id else "default" - queue = django_rq.get_queue(target_queue) - job = queue.fetch_job(rq_id) - if job is not None and "cancel" in job.meta: - return JsonResponse({"status": "finished"}) - data = {} - if job is None: - data["status"] = "unknown" - elif job.is_queued: - data["status"] = "queued" - elif job.is_started: - data["status"] = "started" - data["progress"] = job.meta["progress"] if "progress" in job.meta else "" - elif job.is_finished: - data["status"] = "finished" - job.delete() - else: - data["status"] = "failed" - data["error"] = job.exc_info - job.delete() - - except Exception: - data["status"] = "unknown" - - return JsonResponse(data) diff --git a/cvat/apps/auto_segmentation/__init__.py b/cvat/apps/auto_segmentation/__init__.py deleted file mode 100644 index a0fca4cb..00000000 --- a/cvat/apps/auto_segmentation/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/auto_segmentation/admin.py b/cvat/apps/auto_segmentation/admin.py deleted file mode 100644 index 3c40ebdf..00000000 --- a/cvat/apps/auto_segmentation/admin.py +++ /dev/null @@ -1,8 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -# Register your models here. - diff --git a/cvat/apps/auto_segmentation/apps.py b/cvat/apps/auto_segmentation/apps.py deleted file mode 100644 index 03322710..00000000 --- a/cvat/apps/auto_segmentation/apps.py +++ /dev/null @@ -1,11 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - - -class AutoSegmentationConfig(AppConfig): - name = 'auto_segmentation' - diff --git a/cvat/apps/auto_segmentation/migrations/__init__.py b/cvat/apps/auto_segmentation/migrations/__init__.py deleted file mode 100644 index d8e62e54..00000000 --- a/cvat/apps/auto_segmentation/migrations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - diff --git a/cvat/apps/auto_segmentation/models.py b/cvat/apps/auto_segmentation/models.py deleted file mode 100644 index 37401bdd..00000000 --- a/cvat/apps/auto_segmentation/models.py +++ /dev/null @@ -1,8 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -# Create your models here. - diff --git a/cvat/apps/auto_segmentation/tests.py b/cvat/apps/auto_segmentation/tests.py deleted file mode 100644 index d20a46ab..00000000 --- a/cvat/apps/auto_segmentation/tests.py +++ /dev/null @@ -1,8 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -# Create your tests here. - diff --git a/cvat/apps/auto_segmentation/urls.py b/cvat/apps/auto_segmentation/urls.py deleted file mode 100644 index f84019be..00000000 --- a/cvat/apps/auto_segmentation/urls.py +++ /dev/null @@ -1,14 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('create/task/', views.create), - path('check/task/', views.check), - path('cancel/task/', views.cancel), - path('meta/get', views.get_meta_info), -] diff --git a/cvat/apps/auto_segmentation/views.py b/cvat/apps/auto_segmentation/views.py deleted file mode 100644 index 4b15b094..00000000 --- a/cvat/apps/auto_segmentation/views.py +++ /dev/null @@ -1,310 +0,0 @@ - -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from rest_framework.decorators import api_view -from rules.contrib.views import permission_required, objectgetter -from cvat.apps.authentication.decorators import login_required -from cvat.apps.dataset_manager.task import put_task_data -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.engine.serializers import LabeledDataSerializer -from cvat.apps.engine.frame_provider import FrameProvider - -import django_rq -import os -import rq - -import numpy as np - -from cvat.apps.engine.log import slogger - -import sys -import skimage.io -from skimage.measure import find_contours, approximate_polygon - -def run_tensorflow_auto_segmentation(frame_provider, labels_mapping, treshold): - def _convert_to_int(boolean_mask): - return boolean_mask.astype(np.uint8) - - def _convert_to_segmentation(mask): - contours = find_contours(mask, 0.5) - # only one contour exist in our case - contour = contours[0] - contour = np.flip(contour, axis=1) - # Approximate the contour and reduce the number of points - contour = approximate_polygon(contour, tolerance=2.5) - segmentation = contour.ravel().tolist() - return segmentation - - ## INITIALIZATION - - # workarround for tf.placeholder() is not compatible with eager execution - # https://github.com/tensorflow/tensorflow/issues/18165 - import tensorflow as tf - tf.compat.v1.disable_eager_execution() - - # Root directory of the project - ROOT_DIR = os.environ.get('AUTO_SEGMENTATION_PATH') - # Import Mask RCNN - sys.path.append(ROOT_DIR) # To find local version of the library - import mrcnn.model as modellib - - # Import COCO config - sys.path.append(os.path.join(ROOT_DIR, "samples/coco/")) # To find local version - import coco - - # Directory to save logs and trained model - MODEL_DIR = os.path.join(ROOT_DIR, "logs") - - # Local path to trained weights file - COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5") - if COCO_MODEL_PATH is None: - raise OSError('Model path env not found in the system.') - job = rq.get_current_job() - - ## CONFIGURATION - - class InferenceConfig(coco.CocoConfig): - # Set batch size to 1 since we'll be running inference on - # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU - GPU_COUNT = 1 - IMAGES_PER_GPU = 1 - - # Print config details - config = InferenceConfig() - config.display() - - ## CREATE MODEL AND LOAD TRAINED WEIGHTS - - # Create model object in inference mode. - model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config) - # Load weights trained on MS-COCO - model.load_weights(COCO_MODEL_PATH, by_name=True) - - ## RUN OBJECT DETECTION - result = {} - frames = frame_provider.get_frames(frame_provider.Quality.ORIGINAL) - for image_num, (image_bytes, _) in enumerate(frames): - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(frame_provider) - job.save_meta() - - image = skimage.io.imread(image_bytes) - - # for multiple image detection, "batch size" must be equal to number of images - r = model.detect([image], verbose=1) - - r = r[0] - # "r['rois'][index]" gives bounding box around the object - for index, c_id in enumerate(r['class_ids']): - if c_id in labels_mapping.keys(): - if r['scores'][index] >= treshold: - mask = _convert_to_int(r['masks'][:,:,index]) - segmentation = _convert_to_segmentation(mask) - label = labels_mapping[c_id] - if label not in result: - result[label] = [] - result[label].append( - [image_num, segmentation]) - - return result - -def convert_to_cvat_format(data): - result = { - "tracks": [], - "shapes": [], - "tags": [], - "version": 0, - } - - for label in data: - segments = data[label] - for segment in segments: - result['shapes'].append({ - "type": "polygon", - "label_id": label, - "frame": segment[0], - "points": segment[1], - "z_order": 0, - "group": None, - "occluded": False, - "attributes": [], - }) - - return result - -def create_thread(tid, labels_mapping, user): - try: - # If detected object accuracy bigger than threshold it will returend - TRESHOLD = 0.5 - # Init rq job - job = rq.get_current_job() - job.meta['progress'] = 0 - job.save_meta() - # Get job indexes and segment length - db_task = TaskModel.objects.get(pk=tid) - # Get image list - frame_provider = FrameProvider(db_task.data) - - # Run auto segmentation by tf - result = None - slogger.glob.info("auto segmentation with tensorflow framework for task {}".format(tid)) - result = run_tensorflow_auto_segmentation(frame_provider, labels_mapping, TRESHOLD) - - if result is None: - slogger.glob.info('auto segmentation for task {} canceled by user'.format(tid)) - return - - # Modify data format and save - result = convert_to_cvat_format(result) - serializer = LabeledDataSerializer(data = result) - if serializer.is_valid(raise_exception=True): - put_task_data(tid, result) - slogger.glob.info('auto segmentation for task {} done'.format(tid)) - except Exception as ex: - try: - slogger.task[tid].exception('exception was occured during auto segmentation of the task', exc_info=True) - except Exception: - slogger.glob.exception('exception was occured during auto segmentation of the task {}'.format(tid), exc_info=True) - raise ex - -@api_view(['POST']) -@login_required -def get_meta_info(request): - try: - queue = django_rq.get_queue('low') - tids = request.data - result = {} - for tid in tids: - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is not None: - result[tid] = { - "active": job.is_queued or job.is_started, - "success": not job.is_failed - } - - return JsonResponse(result) - except Exception as ex: - slogger.glob.exception('exception was occured during tf meta request', exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def create(request, tid): - slogger.glob.info('auto segmentation create request for task {}'.format(tid)) - try: - db_task = TaskModel.objects.get(pk=tid) - queue = django_rq.get_queue('low') - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is not None and (job.is_started or job.is_queued): - raise Exception("The process is already running") - - db_labels = db_task.label_set.prefetch_related('attributespec_set').all() - db_labels = {db_label.id:db_label.name for db_label in db_labels} - - # COCO Labels - auto_segmentation_labels = { "BG": 0, - "person": 1, "bicycle": 2, "car": 3, "motorcycle": 4, "airplane": 5, - "bus": 6, "train": 7, "truck": 8, "boat": 9, "traffic_light": 10, - "fire_hydrant": 11, "stop_sign": 12, "parking_meter": 13, "bench": 14, - "bird": 15, "cat": 16, "dog": 17, "horse": 18, "sheep": 19, "cow": 20, - "elephant": 21, "bear": 22, "zebra": 23, "giraffe": 24, "backpack": 25, - "umbrella": 26, "handbag": 27, "tie": 28, "suitcase": 29, "frisbee": 30, - "skis": 31, "snowboard": 32, "sports_ball": 33, "kite": 34, "baseball_bat": 35, - "baseball_glove": 36, "skateboard": 37, "surfboard": 38, "tennis_racket": 39, - "bottle": 40, "wine_glass": 41, "cup": 42, "fork": 43, "knife": 44, "spoon": 45, - "bowl": 46, "banana": 47, "apple": 48, "sandwich": 49, "orange": 50, "broccoli": 51, - "carrot": 52, "hot_dog": 53, "pizza": 54, "donut": 55, "cake": 56, "chair": 57, - "couch": 58, "potted_plant": 59, "bed": 60, "dining_table": 61, "toilet": 62, - "tv": 63, "laptop": 64, "mouse": 65, "remote": 66, "keyboard": 67, "cell_phone": 68, - "microwave": 69, "oven": 70, "toaster": 71, "sink": 72, "refrigerator": 73, - "book": 74, "clock": 75, "vase": 76, "scissors": 77, "teddy_bear": 78, "hair_drier": 79, - "toothbrush": 80 - } - - labels_mapping = {} - for key, labels in db_labels.items(): - if labels in auto_segmentation_labels.keys(): - labels_mapping[auto_segmentation_labels[labels]] = key - - if not len(labels_mapping.values()): - raise Exception('No labels found for auto segmentation') - - # Run auto segmentation job - queue.enqueue_call(func=create_thread, - args=(tid, labels_mapping, request.user), - job_id='auto_segmentation.create/{}'.format(tid), - timeout=604800) # 7 days - - slogger.task[tid].info('tensorflow segmentation job enqueued with labels {}'.format(labels_mapping)) - - except Exception as ex: - try: - slogger.task[tid].exception("exception was occured during tensorflow segmentation request", exc_info=True) - except Exception: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() - -@login_required -@permission_required(perm=['engine.task.access'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def check(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is not None and 'cancel' in job.meta: - return JsonResponse({'status': 'finished'}) - data = {} - if job is None: - data['status'] = 'unknown' - elif job.is_queued: - data['status'] = 'queued' - elif job.is_started: - data['status'] = 'started' - data['progress'] = job.meta['progress'] - elif job.is_finished: - data['status'] = 'finished' - job.delete() - else: - data['status'] = 'failed' - data['stderr'] = job.exc_info - job.delete() - - except Exception: - data['status'] = 'unknown' - - return JsonResponse(data) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def cancel(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is None or job.is_finished or job.is_failed: - raise Exception('Task is not being segmented currently') - elif 'cancel' not in job.meta: - job.meta['cancel'] = True - job.save() - - except Exception as ex: - try: - slogger.task[tid].exception("cannot cancel tensorflow segmentation for task #{}".format(tid), exc_info=True) - except Exception: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() diff --git a/cvat/apps/dataset_manager/annotation.py b/cvat/apps/dataset_manager/annotation.py index df66c99b..30393dd5 100644 --- a/cvat/apps/dataset_manager/annotation.py +++ b/cvat/apps/dataset_manager/annotation.py @@ -5,6 +5,7 @@ from copy import copy, deepcopy import numpy as np +from itertools import chain from scipy.optimize import linear_sum_assignment from shapely import geometry @@ -315,7 +316,10 @@ class ShapeManager(ObjectManager): def _calc_objects_similarity(obj0, obj1, start_frame, overlap): def _calc_polygons_similarity(p0, p1): overlap_area = p0.intersection(p1).area - return overlap_area / (p0.area + p1.area - overlap_area) + if p0.area == 0 or p1.area == 0: # a line with many points + return 0 + else: + return overlap_area / (p0.area + p1.area - overlap_area) has_same_type = obj0["type"] == obj1["type"] has_same_label = obj0.get("label_id") == obj1.get("label_id") @@ -327,7 +331,7 @@ class ShapeManager(ObjectManager): return _calc_polygons_similarity(p0, p1) elif obj0["type"] == ShapeType.POLYGON: p0 = geometry.Polygon(pairwise(obj0["points"])) - p1 = geometry.Polygon(pairwise(obj0["points"])) + p1 = geometry.Polygon(pairwise(obj1["points"])) return _calc_polygons_similarity(p0, p1) else: @@ -442,34 +446,284 @@ class TrackManager(ObjectManager): @staticmethod def get_interpolated_shapes(track, start_frame, end_frame): - def interpolate(shape0, shape1): + def copy_shape(source, frame, points=None): + copied = deepcopy(source) + copied["keyframe"] = False + copied["frame"] = frame + if points is not None: + copied["points"] = points + return copied + + def simple_interpolation(shape0, shape1): + shapes = [] + distance = shape1["frame"] - shape0["frame"] + diff = np.subtract(shape1["points"], shape0["points"]) + + for frame in range(shape0["frame"] + 1, shape1["frame"]): + offset = (frame - shape0["frame"]) / distance + points = None + if shape1["outside"]: + points = np.asarray(shape0["points"]) + else: + points = shape0["points"] + diff * offset + + shapes.append(copy_shape(shape0, frame, points.tolist())) + + return shapes + + def points_interpolation(shape0, shape1): + if len(shape0["points"]) == 2 and len(shape1["points"]) == 2: + return simple_interpolation(shape0, shape1) + else: + shapes = [] + for frame in range(shape0["frame"] + 1, shape1["frame"]): + shapes.append(copy_shape(shape0, frame)) + + return shapes + + def interpolate_position(left_position, right_position, offset): + def to_array(points): + return np.asarray( + list(map(lambda point: [point["x"], point["y"]], points)) + ).flatten() + + def to_points(array): + return list(map( + lambda point: {"x": point[0], "y": point[1]}, np.asarray(array).reshape(-1, 2) + )) + + def curve_length(points): + length = 0 + for i in range(1, len(points)): + dx = points[i]["x"] - points[i - 1]["x"] + dy = points[i]["y"] - points[i - 1]["y"] + length += np.sqrt(dx ** 2 + dy ** 2) + return length + + def curve_to_offset_vec(points, length): + offset_vector = [0] + accumulated_length = 0 + for i in range(1, len(points)): + dx = points[i]["x"] - points[i - 1]["x"] + dy = points[i]["y"] - points[i - 1]["y"] + accumulated_length += np.sqrt(dx ** 2 + dy ** 2) + offset_vector.append(accumulated_length / length) + + return offset_vector + + def find_nearest_pair(value, curve): + minimum = [0, abs(value - curve[0])] + for i in range(1, len(curve)): + distance = abs(value - curve[i]) + if distance < minimum[1]: + minimum = [i, distance] + + return minimum[0] + + def match_left_right(left_curve, right_curve): + matching = {} + for i, left_curve_item in enumerate(left_curve): + matching[i] = [find_nearest_pair(left_curve_item, right_curve)] + return matching + + def match_right_left(left_curve, right_curve, left_right_matching): + matched_right_points = list(chain.from_iterable(left_right_matching.values())) + unmatched_right_points = filter(lambda x: x not in matched_right_points, range(len(right_curve))) + updated_matching = deepcopy(left_right_matching) + + for right_point in unmatched_right_points: + left_point = find_nearest_pair(right_curve[right_point], left_curve) + updated_matching[left_point].append(right_point) + + for key, value in updated_matching.items(): + updated_matching[key] = sorted(value) + + return updated_matching + + def reduce_interpolation(interpolated_points, matching, left_points, right_points): + def average_point(points): + sumX = 0 + sumY = 0 + for point in points: + sumX += point["x"] + sumY += point["y"] + + return { + "x": sumX / len(points), + "y": sumY / len(points) + } + + def compute_distance(point1, point2): + return np.sqrt( + ((point1["x"] - point2["x"])) ** 2 + + ((point1["y"] - point2["y"]) ** 2) + ) + + def minimize_segment(base_length, N, start_interpolated, stop_interpolated): + threshold = base_length / (2 * N) + minimized = [interpolated_points[start_interpolated]] + latest_pushed = start_interpolated + for i in range(start_interpolated + 1, stop_interpolated): + distance = compute_distance( + interpolated_points[latest_pushed], interpolated_points[i] + ) + + if distance >= threshold: + minimized.append(interpolated_points[i]) + latest_pushed = i + + minimized.append(interpolated_points[stop_interpolated]) + + if len(minimized) == 2: + distance = compute_distance( + interpolated_points[start_interpolated], + interpolated_points[stop_interpolated] + ) + + if distance < threshold: + return [average_point(minimized)] + + return minimized + + reduced = [] + interpolated_indexes = {} + accumulated = 0 + for i in range(len(left_points)): + interpolated_indexes[i] = [] + for _ in range(len(matching[i])): + interpolated_indexes[i].append(accumulated) + accumulated += 1 + + def left_segment(start, stop): + start_interpolated = interpolated_indexes[start][0] + stop_interpolated = interpolated_indexes[stop][0] + + if start_interpolated == stop_interpolated: + reduced.append(interpolated_points[start_interpolated]) + return + + base_length = curve_length(left_points[start: stop + 1]) + N = stop - start + 1 + + reduced.extend( + minimize_segment(base_length, N, start_interpolated, stop_interpolated) + ) + + + def right_segment(left_point): + start = matching[left_point][0] + stop = matching[left_point][-1] + start_interpolated = interpolated_indexes[left_point][0] + stop_interpolated = interpolated_indexes[left_point][-1] + base_length = curve_length(right_points[start: stop + 1]) + N = stop - start + 1 + + reduced.extend( + minimize_segment(base_length, N, start_interpolated, stop_interpolated) + ) + + previous_opened = None + for i in range(len(left_points)): + if len(matching[i]) == 1: + if previous_opened is not None: + if matching[i][0] == matching[previous_opened][0]: + continue + else: + start = previous_opened + stop = i - 1 + left_segment(start, stop) + previous_opened = i + else: + previous_opened = i + else: + if previous_opened is not None: + start = previous_opened + stop = i - 1 + left_segment(start, stop) + previous_opened = None + + right_segment(i) + + if previous_opened is not None: + left_segment(previous_opened, len(left_points) - 1) + + return reduced + + left_points = to_points(left_position["points"]) + right_points = to_points(right_position["points"]) + left_offset_vec = curve_to_offset_vec(left_points, curve_length(left_points)) + right_offset_vec = curve_to_offset_vec(right_points, curve_length(right_points)) + + matching = match_left_right(left_offset_vec, right_offset_vec) + completed_matching = match_right_left( + left_offset_vec, right_offset_vec, matching + ) + + interpolated_points = [] + for left_point_index, left_point in enumerate(left_points): + for right_point_index in completed_matching[left_point_index]: + right_point = right_points[right_point_index] + interpolated_points.append({ + "x": left_point["x"] + (right_point["x"] - left_point["x"]) * offset, + "y": left_point["y"] + (right_point["y"] - left_point["y"]) * offset + }) + + reducedPoints = reduce_interpolation( + interpolated_points, + completed_matching, + left_points, + right_points + ) + + return to_array(reducedPoints).tolist() + + def polyshape_interpolation(shape0, shape1): shapes = [] - is_same_type = shape0["type"] == shape1["type"] is_polygon = shape0["type"] == ShapeType.POLYGON - is_polyline = shape0["type"] == ShapeType.POLYLINE - is_same_size = len(shape0["points"]) == len(shape1["points"]) - if not is_same_type or is_polygon or is_polyline or not is_same_size: - shape0 = TrackManager.normalize_shape(shape0) - shape1 = TrackManager.normalize_shape(shape1) + if is_polygon: + shape0["points"].extend(shape0["points"][:2]) + shape1["points"].extend(shape1["points"][:2]) distance = shape1["frame"] - shape0["frame"] - step = np.subtract(shape1["points"], shape0["points"]) / distance for frame in range(shape0["frame"] + 1, shape1["frame"]): - off = frame - shape0["frame"] + offset = (frame - shape0["frame"]) / distance + points = None if shape1["outside"]: - points = np.asarray(shape0["points"]).reshape(-1, 2) - else: - points = (shape0["points"] + step * off).reshape(-1, 2) - shape = deepcopy(shape0) - if len(points) == 1: - shape["points"] = points.flatten() + points = np.asarray(shape0["points"]) else: - broken_line = geometry.LineString(points).simplify(0.05, False) - shape["points"] = [x for p in broken_line.coords for x in p] + points = interpolate_position(shape0, shape1, offset) + + shapes.append(copy_shape(shape0, frame, points)) + + if is_polygon: + shape0["points"] = shape0["points"][:-2] + shape1["points"] = shape1["points"][:-2] + for shape in shapes: + shape["points"] = shape["points"][:-2] + + return shapes + + def interpolate(shape0, shape1): + is_same_type = shape0["type"] == shape1["type"] + is_rectangle = shape0["type"] == ShapeType.RECTANGLE + is_cuboid = shape0["type"] == ShapeType.CUBOID + is_polygon = shape0["type"] == ShapeType.POLYGON + is_polyline = shape0["type"] == ShapeType.POLYLINE + is_points = shape0["type"] == ShapeType.POINTS + + if not is_same_type: + raise NotImplementedError() + + shapes = [] + if is_rectangle or is_cuboid: + shapes = simple_interpolation(shape0, shape1) + elif is_points: + shapes = points_interpolation(shape0, shape1) + elif is_polygon or is_polyline: + shapes = polyshape_interpolation(shape0, shape1) + else: + raise NotImplementedError() - shape["keyframe"] = False - shape["frame"] = frame - shapes.append(shape) return shapes if track.get("interpolated_shapes"): diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index 55f98acc..30ec8bd9 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -5,12 +5,14 @@ import os.path as osp from collections import OrderedDict, namedtuple +from pathlib import Path from django.utils import timezone import datumaro.components.extractor as datumaro from cvat.apps.engine.frame_provider import FrameProvider from cvat.apps.engine.models import AttributeType, ShapeType +from datumaro.util import cast from datumaro.util.image import Image from .annotation import AnnotationManager, TrackManager @@ -19,13 +21,13 @@ from .annotation import AnnotationManager, TrackManager class TaskData: Attribute = namedtuple('Attribute', 'name, value') LabeledShape = namedtuple( - 'LabeledShape', 'type, frame, label, points, occluded, attributes, group, z_order') + 'LabeledShape', 'type, frame, label, points, occluded, attributes, source, group, z_order') LabeledShape.__new__.__defaults__ = (0, 0) TrackedShape = namedtuple( - 'TrackedShape', 'type, frame, points, occluded, outside, keyframe, attributes, group, z_order, label, track_id') - TrackedShape.__new__.__defaults__ = (0, 0, None, 0) - Track = namedtuple('Track', 'label, group, shapes') - Tag = namedtuple('Tag', 'frame, label, attributes, group') + 'TrackedShape', 'type, frame, points, occluded, outside, keyframe, attributes, source, group, z_order, label, track_id') + TrackedShape.__new__.__defaults__ = ('manual', 0, 0, None, 0) + Track = namedtuple('Track', 'label, group, source, shapes') + Tag = namedtuple('Tag', 'frame, label, attributes, source, group') Tag.__new__.__defaults__ = (0, ) Frame = namedtuple( 'Frame', 'idx, frame, name, width, height, labeled_shapes, tags') @@ -47,7 +49,8 @@ class TaskData: (db_label.id, db_label) for db_label in db_labels) self._attribute_mapping = {db_label.id: { - 'mutable': {}, 'immutable': {}} for db_label in db_labels} + 'mutable': {}, 'immutable': {}, 'spec': {}} + for db_label in db_labels} for db_label in db_labels: for db_attribute in db_label.attributespec_set.all(): @@ -55,6 +58,7 @@ class TaskData: self._attribute_mapping[db_label.id]['mutable'][db_attribute.id] = db_attribute.name else: self._attribute_mapping[db_label.id]['immutable'][db_attribute.id] = db_attribute.name + self._attribute_mapping[db_label.id]['spec'][db_attribute.id] = db_attribute self._attribute_mapping_merged = {} for label_id, attr_mapping in self._attribute_mapping.items(): @@ -70,7 +74,7 @@ class TaskData: for db_label in self._label_mapping.values(): if label_name == db_label.name: return db_label.id - return None + raise ValueError("Label {!r} is not registered for this task".format(label_name)) def _get_label_name(self, label_id): return self._label_mapping[label_id].name @@ -97,24 +101,35 @@ class TaskData: def _get_immutable_attribute_id(self, label_id, attribute_name): return self._get_attribute_id(label_id, attribute_name, 'immutable') + def abs_frame_id(self, relative_id): + if relative_id not in range(0, self._db_task.data.size): + raise ValueError("Unknown internal frame id %s" % relative_id) + return relative_id * self._frame_step + self._db_task.data.start_frame + + def rel_frame_id(self, absolute_id): + d, m = divmod( + absolute_id - self._db_task.data.start_frame, self._frame_step) + if m or d not in range(0, self._db_task.data.size): + raise ValueError("Unknown frame %s" % absolute_id) + return d + def _init_frame_info(self): if hasattr(self._db_task.data, 'video'): self._frame_info = {frame: { - "path": "frame_{:06d}".format( - self._db_task.data.start_frame + frame * self._frame_step), + "path": "frame_{:06d}".format(self.abs_frame_id(frame)), "width": self._db_task.data.video.width, "height": self._db_task.data.video.height, } for frame in range(self._db_task.data.size)} else: - self._frame_info = {db_image.frame: { + self._frame_info = {self.rel_frame_id(db_image.frame): { "path": db_image.path, "width": db_image.width, "height": db_image.height, } for db_image in self._db_task.data.images.all()} self._frame_mapping = { - self._get_filename(info["path"]): frame - for frame, info in self._frame_info.items() + self._get_filename(info["path"]): frame_number + for frame_number, info in self._frame_info.items() } def _init_meta(self): @@ -137,6 +152,7 @@ class TaskData: ("labels", [ ("label", OrderedDict([ ("name", db_label.name), + ("color", db_label.color), ("attributes", [ ("attribute", OrderedDict([ ("name", db_attr.name), @@ -193,8 +209,7 @@ class TaskData: def _export_tracked_shape(self, shape): return TaskData.TrackedShape( type=shape["type"], - frame=self._db_task.data.start_frame + - shape["frame"] * self._frame_step, + frame=self.abs_frame_id(shape["frame"]), label=self._get_label_name(shape["label_id"]), points=shape["points"], occluded=shape["occluded"], @@ -203,6 +218,7 @@ class TaskData: outside=shape.get("outside", False), keyframe=shape.get("keyframe", True), track_id=shape["track_id"], + source=shape.get("source", "manual"), attributes=self._export_attributes(shape["attributes"]), ) @@ -210,21 +226,21 @@ class TaskData: return TaskData.LabeledShape( type=shape["type"], label=self._get_label_name(shape["label_id"]), - frame=self._db_task.data.start_frame + - shape["frame"] * self._frame_step, + frame=self.abs_frame_id(shape["frame"]), points=shape["points"], occluded=shape["occluded"], z_order=shape.get("z_order", 0), group=shape.get("group", 0), + source=shape["source"], attributes=self._export_attributes(shape["attributes"]), ) def _export_tag(self, tag): return TaskData.Tag( - frame=self._db_task.data.start_frame + - tag["frame"] * self._frame_step, + frame=self.abs_frame_id(tag["frame"]), label=self._get_label_name(tag["label_id"]), group=tag.get("group", 0), + source=tag["source"], attributes=self._export_attributes(tag["attributes"]), ) @@ -232,7 +248,7 @@ class TaskData: frames = {} def get_frame(idx): frame_info = self._frame_info[idx] - frame = self._db_task.data.start_frame + idx * self._frame_step + frame = self.abs_frame_id(idx) if frame not in frames: frames[frame] = TaskData.Frame( idx=idx, @@ -278,11 +294,13 @@ class TaskData: tracked_shape["attributes"] += track["attributes"] tracked_shape["track_id"] = idx tracked_shape["group"] = track["group"] + tracked_shape["source"] = track["source"] tracked_shape["label_id"] = track["label_id"] yield TaskData.Track( label=self._get_label_name(track["label_id"]), group=track["group"], + source=track["source"], shapes=[self._export_tracked_shape(shape) for shape in tracked_shapes], ) @@ -299,8 +317,7 @@ class TaskData: def _import_tag(self, tag): _tag = tag._asdict() label_id = self._get_label_id(_tag.pop('label')) - _tag['frame'] = (int(_tag['frame']) - - self._db_task.data.start_frame) // self._frame_step + _tag['frame'] = self.rel_frame_id(int(_tag['frame'])) _tag['label_id'] = label_id _tag['attributes'] = [self._import_attribute(label_id, attrib) for attrib in _tag['attributes'] @@ -308,39 +325,57 @@ class TaskData: return _tag def _import_attribute(self, label_id, attribute): - return { - 'spec_id': self._get_attribute_id(label_id, attribute.name), - 'value': attribute.value, - } + spec_id = self._get_attribute_id(label_id, attribute.name) + value = attribute.value + + if spec_id: + spec = self._attribute_mapping[label_id]['spec'][spec_id] + + try: + if spec.input_type == AttributeType.NUMBER: + pass # no extra processing required + elif spec.input_type == AttributeType.CHECKBOX: + if isinstance(value, str): + value = value.lower() + assert value in {'true', 'false'} + elif isinstance(value, (bool, int, float)): + value = 'true' if value else 'false' + else: + raise ValueError("Unexpected attribute value") + except Exception as e: + raise Exception("Failed to convert attribute '%s'='%s': %s" % + (self._get_label_name(label_id), value, e)) + + return { 'spec_id': spec_id, 'value': value } def _import_shape(self, shape): _shape = shape._asdict() label_id = self._get_label_id(_shape.pop('label')) - _shape['frame'] = (int(_shape['frame']) - - self._db_task.data.start_frame) // self._frame_step + _shape['frame'] = self.rel_frame_id(int(_shape['frame'])) _shape['label_id'] = label_id _shape['attributes'] = [self._import_attribute(label_id, attrib) for attrib in _shape['attributes'] if self._get_attribute_id(label_id, attrib.name)] + _shape['points'] = list(map(float, _shape['points'])) return _shape def _import_track(self, track): _track = track._asdict() label_id = self._get_label_id(_track.pop('label')) - _track['frame'] = (min(int(shape.frame) for shape in _track['shapes']) - - self._db_task.data.start_frame) // self._frame_step + _track['frame'] = self.rel_frame_id( + min(int(shape.frame) for shape in _track['shapes'])) _track['label_id'] = label_id _track['attributes'] = [] _track['shapes'] = [shape._asdict() for shape in _track['shapes']] for shape in _track['shapes']: - shape['frame'] = (int(shape['frame']) - \ - self._db_task.data.start_frame) // self._frame_step + shape['frame'] = self.rel_frame_id(int(shape['frame'])) _track['attributes'] = [self._import_attribute(label_id, attrib) for attrib in shape['attributes'] if self._get_immutable_attribute_id(label_id, attrib.name)] shape['attributes'] = [self._import_attribute(label_id, attrib) for attrib in shape['attributes'] if self._get_mutable_attribute_id(label_id, attrib.name)] + shape['points'] = list(map(float, shape['points'])) return _track @@ -392,21 +427,33 @@ class TaskData: @staticmethod def _get_filename(path): - return osp.splitext(osp.basename(path))[0] - - def match_frame(self, filename): - # try to match by filename - _filename = self._get_filename(filename) - if _filename in self._frame_mapping: - return self._frame_mapping[_filename] - - raise Exception( - "Cannot match filename or determine frame number for {} filename".format(filename)) + return osp.splitext(path)[0] + + def match_frame(self, path, root_hint=None): + path = self._get_filename(path) + match = self._frame_mapping.get(path) + if not match and root_hint and not path.startswith(root_hint): + path = osp.join(root_hint, path) + match = self._frame_mapping.get(path) + return match + + def match_frame_fuzzy(self, path): + # Preconditions: + # - The input dataset is full, i.e. all items present. Partial dataset + # matching can't be correct for all input cases. + # - path is the longest path of input dataset in terms of path parts + + path = Path(self._get_filename(path)).parts + for p, v in self._frame_mapping.items(): + if Path(p).parts[-len(path):] == path: # endswith() for paths + return v + return None class CvatTaskDataExtractor(datumaro.SourceExtractor): - def __init__(self, task_data, include_images=False): + def __init__(self, task_data, include_images=False, include_outside=False): super().__init__() self._categories = self._load_categories(task_data) + self._include_outside = include_outside dm_items = [] @@ -423,8 +470,9 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): size=(frame_data.height, frame_data.width) ) dm_anno = self._read_cvat_anno(frame_data, task_data) - dm_item = datumaro.DatasetItem(id=frame_data.frame, - annotations=dm_anno, image=dm_image) + dm_item = datumaro.DatasetItem(id=osp.splitext(frame_data.name)[0], + annotations=dm_anno, image=dm_image, + attributes={'frame': frame_data.frame}) dm_items.append(dm_item) self._items = dm_items @@ -443,8 +491,7 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): def _load_categories(cvat_anno): categories = {} - label_categories = datumaro.LabelCategories( - attributes=['occluded', 'z_order']) + label_categories = datumaro.LabelCategories(attributes=['occluded']) for _, label in cvat_anno.meta['task']['labels']: label_categories.add(label['name']) @@ -485,7 +532,7 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): return dm_attr for tag_obj in cvat_frame_anno.tags: - anno_group = tag_obj.group + anno_group = tag_obj.group or 0 anno_label = map_label(tag_obj.label) anno_attr = convert_attrs(tag_obj.label, tag_obj.attributes) @@ -494,7 +541,7 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): item_anno.append(anno) for shape_obj in cvat_frame_anno.labeled_shapes: - anno_group = shape_obj.group + anno_group = shape_obj.group or 0 anno_label = map_label(shape_obj.label) anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes) anno_attr['occluded'] = shape_obj.occluded @@ -503,6 +550,9 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): anno_attr['track_id'] = shape_obj.track_id anno_attr['keyframe'] = shape_obj.keyframe + if not self._include_outside and shape_obj.outside: + continue + anno_points = shape_obj.points if shape_obj.type == ShapeType.POINTS: anno = datumaro.Points(anno_points, @@ -530,32 +580,37 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor): return item_anno -def match_frame(item, task_data): +def match_dm_item(item, task_data, root_hint=None): is_video = task_data.meta['task']['mode'] == 'interpolation' frame_number = None - if frame_number is None: - try: - frame_number = task_data.match_frame(item.id) - except Exception: - pass if frame_number is None and item.has_image: - try: - frame_number = task_data.match_frame(item.image.filename) - except Exception: - pass + frame_number = task_data.match_frame(item.image.path, root_hint) if frame_number is None: - try: - frame_number = int(item.id) - except Exception: - pass - if frame_number is None and is_video and item.id.startswith('frame_'): - frame_number = int(item.id[len('frame_'):]) + frame_number = task_data.match_frame(item.id, root_hint) + if frame_number is None: + frame_number = cast(item.attributes.get('frame', item.id), int) + if frame_number is None and is_video: + frame_number = cast(osp.basename(item.id)[len('frame_'):], int) + if not frame_number in task_data.frame_info: raise Exception("Could not match item id: '%s' with any task frame" % item.id) return frame_number +def find_dataset_root(dm_dataset, task_data): + longest_path = max(dm_dataset, key=lambda x: len(Path(x.id).parts)).id + longest_match = task_data.match_frame_fuzzy(longest_path) + if longest_match is None: + return None + + longest_match = osp.dirname(task_data.frame_info[longest_match]['path']) + prefix = longest_match[:-len(osp.dirname(longest_path)) or None] + if prefix.endswith('/'): + prefix = prefix[:-1] + return prefix + + def import_dm_annotations(dm_dataset, task_data): shapes = { datumaro.AnnotationType.bbox: ShapeType.RECTANGLE, @@ -564,10 +619,16 @@ def import_dm_annotations(dm_dataset, task_data): datumaro.AnnotationType.points: ShapeType.POINTS, } + if len(dm_dataset) == 0: + return + label_cat = dm_dataset.categories()[datumaro.AnnotationType.label] + root_hint = find_dataset_root(dm_dataset, task_data) + for item in dm_dataset: - frame_number = match_frame(item, task_data) + frame_number = task_data.abs_frame_id( + match_dm_item(item, task_data, root_hint=root_hint)) # do not store one-item groups group_map = {0: 0} @@ -595,6 +656,7 @@ def import_dm_annotations(dm_dataset, task_data): occluded=ann.attributes.get('occluded') == True, z_order=ann.z_order, group=group_map.get(ann.group, 0), + source='manual', attributes=[task_data.Attribute(name=n, value=str(v)) for n, v in ann.attributes.items()], )) @@ -603,6 +665,7 @@ def import_dm_annotations(dm_dataset, task_data): frame=frame_number, label=label_cat.items[ann.label].name, group=group_map.get(ann.group, 0), + source='manual', attributes=[task_data.Attribute(name=n, value=str(v)) for n, v in ann.attributes.items()], )) diff --git a/cvat/apps/dataset_manager/formats/coco.py b/cvat/apps/dataset_manager/formats/coco.py index 41d6343a..84472d3a 100644 --- a/cvat/apps/dataset_manager/formats/coco.py +++ b/cvat/apps/dataset_manager/formats/coco.py @@ -18,9 +18,8 @@ def _export(dst_file, task_data, save_images=False): extractor = CvatTaskDataExtractor(task_data, include_images=save_images) extractor = Dataset.from_extractors(extractor) # apply lazy transforms with TemporaryDirectory() as temp_dir: - converter = dm_env.make_converter('coco_instances', - save_images=save_images) - converter(extractor, save_dir=temp_dir) + dm_env.converters.get('coco_instances').convert(extractor, + save_dir=temp_dir, save_images=save_images) make_zip_archive(temp_dir, dst_file) diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index 42e0d6cc..ae1257c2 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -9,8 +9,10 @@ from collections import OrderedDict from glob import glob from tempfile import TemporaryDirectory +from cvat.apps.dataset_manager.bindings import match_dm_item from cvat.apps.dataset_manager.util import make_zip_archive from cvat.apps.engine.frame_provider import FrameProvider +from datumaro.components.extractor import DatasetItem from datumaro.util.image import save_image from .registry import exporter, importer @@ -188,6 +190,7 @@ def dump_as_cvat_annotation(file_object, annotations): dump_data = OrderedDict([ ("label", shape.label), ("occluded", str(int(shape.occluded))), + ("source", shape.source), ]) if shape.type == "rectangle": @@ -267,6 +270,7 @@ def dump_as_cvat_annotation(file_object, annotations): for tag in frame_annotation.tags: tag_data = OrderedDict([ ("label", tag.label), + ("source", tag.source), ]) if tag.group: tag_data["group_id"] = str(tag.group) @@ -292,6 +296,7 @@ def dump_as_cvat_interpolation(file_object, annotations): dump_data = OrderedDict([ ("id", str(track_id)), ("label", track.label), + ("source", track.source), ]) if track.group: @@ -383,6 +388,7 @@ def dump_as_cvat_interpolation(file_object, annotations): dump_track(counter, annotations.Track( label=shape.label, group=shape.group, + source=shape.source, shapes=[annotations.TrackedShape( type=shape.type, points=shape.points, @@ -428,11 +434,12 @@ def load(file_object, annotations): track = annotations.Track( label=el.attrib['label'], group=int(el.attrib.get('group_id', 0)), + source=el.attrib.get('source', 'manual'), shapes=[], ) elif el.tag == 'image': image_is_opened = True - frame_id = int(el.attrib['id']) + frame_id = match_dm_item(DatasetItem(id=el.attrib['id'], image=el.attrib['name']), annotations) elif el.tag in supported_shapes and (track is not None or image_is_opened): attributes = [] shape = { @@ -446,6 +453,7 @@ def load(file_object, annotations): 'label': el.attrib['label'], 'group': int(el.attrib.get('group_id', 0)), 'attributes': attributes, + 'source': str(el.attrib.get('source', 'manual')) } elif ev == 'end': if el.tag == 'attribute' and attributes is not None: @@ -462,6 +470,7 @@ def load(file_object, annotations): shape['frame'] = frame_id shape['label'] = el.attrib['label'] shape['group'] = int(el.attrib.get('group_id', 0)) + shape['source'] = str(el.attrib.get('source', 'manual')) shape['type'] = 'rectangle' if el.tag == 'box' else el.tag shape['occluded'] = el.attrib['occluded'] == '1' @@ -518,7 +527,6 @@ def _export(dst_file, task_data, anno_callback, save_images=False): if save_images: img_dir = osp.join(temp_dir, 'images') - os.makedirs(img_dir) frame_provider = FrameProvider(task_data.db_task.data) frames = frame_provider.get_frames( frame_provider.Quality.ORIGINAL, @@ -527,10 +535,10 @@ def _export(dst_file, task_data, anno_callback, save_images=False): frame_name = task_data.frame_info[frame_id]['path'] if '.' in frame_name: save_image(osp.join(img_dir, frame_name), - frame_data, jpeg_quality=100) + frame_data, jpeg_quality=100, create_dir=True) else: save_image(osp.join(img_dir, frame_name + '.png'), - frame_data) + frame_data, create_dir=True) make_zip_archive(temp_dir, dst_file) @@ -556,4 +564,4 @@ def _import(src_file, task_data): for p in anno_paths: load(p, task_data) else: - load(src_file, task_data) \ No newline at end of file + load(src_file, task_data) diff --git a/cvat/apps/dataset_manager/formats/datumaro/__init__.py b/cvat/apps/dataset_manager/formats/datumaro/__init__.py index 59c423d0..9dd3f9ab 100644 --- a/cvat/apps/dataset_manager/formats/datumaro/__init__.py +++ b/cvat/apps/dataset_manager/formats/datumaro/__init__.py @@ -48,11 +48,10 @@ class DatumaroProjectExporter: def _export(self, task_data, save_dir, save_images=False): dataset = CvatTaskDataExtractor(task_data, include_images=save_images) - converter = dm_env.make_converter('datumaro_project', - save_images=save_images, - config={ 'project_name': task_data.db_task.name, } + dm_env.converters.get('datumaro_project').convert(dataset, + save_dir=save_dir, save_images=save_images, + project_config={ 'project_name': task_data.db_task.name, } ) - converter(dataset, save_dir=save_dir) project = Project.load(save_dir) target_dir = project.config.project_dir diff --git a/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py b/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py index b54cce3f..9a7a9f06 100644 --- a/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py +++ b/cvat/apps/dataset_manager/formats/datumaro/export_templates/plugins/cvat_rest_api_task_images.py @@ -42,32 +42,19 @@ class cvat_rest_api_task_images(SourceExtractor): frame_ids=[item_id], outdir=self._cache_dir, quality='original') def _connect(self): - if self._session is not None: + if self._cvat_cli is not None: return - session = None - try: - print("Enter credentials for '%s' to read task data:" % \ - (self._config.server_url)) - username = input('User: ') - password = getpass.getpass() + print("Enter credentials for '%s' to read task data:" % \ + (self._config.server_url)) + username = input('User: ') + password = getpass.getpass() - session = requests.Session() - session.auth = (username, password) + session = requests.Session() - api = CVAT_API_V1(self._config.server_url) - cli = CVAT_CLI(session, api) - - self._session = session - self._cvat_cli = cli - except Exception: - if session is not None: - session.close() - - def __del__(self): - if hasattr(self, '_session'): - if self._session is not None: - self._session.close() + api = CVAT_API_V1(self._config.server_url) + cli = CVAT_CLI(session, api, credentials=(username, password)) + self._cvat_cli = cli @staticmethod def _image_loader(item_id, extractor): @@ -100,16 +87,14 @@ class cvat_rest_api_task_images(SourceExtractor): if entry.get('height') and entry.get('width'): size = (entry['height'], entry['width']) image = Image(data=self._make_image_loader(item_id), - path=item_filename, size=size) - item = DatasetItem(id=item_id, image=image) + path=self._image_local_path(item_id), size=size) + item = DatasetItem(id=osp.splitext(item_filename)[0], image=image) items.append((item.id, item)) - items = sorted(items, key=lambda e: int(e[0])) items = OrderedDict(items) self._items = items self._cvat_cli = None - self._session = None def __iter__(self): for item in self._items.values(): diff --git a/cvat/apps/dataset_manager/formats/labelme.py b/cvat/apps/dataset_manager/formats/labelme.py index 9ea1a76b..d3bd074d 100644 --- a/cvat/apps/dataset_manager/formats/labelme.py +++ b/cvat/apps/dataset_manager/formats/labelme.py @@ -17,12 +17,10 @@ from .registry import dm_env, exporter, importer @exporter(name='LabelMe', ext='ZIP', version='3.0') def _export(dst_file, task_data, save_images=False): extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - envt = dm_env.transforms - extractor = extractor.transform(envt.get('id_from_image_name')) extractor = Dataset.from_extractors(extractor) # apply lazy transforms with TemporaryDirectory() as temp_dir: - converter = dm_env.make_converter('label_me', save_images=save_images) - converter(extractor, save_dir=temp_dir) + dm_env.converters.get('label_me').convert(extractor, save_dir=temp_dir, + save_images=save_images) make_zip_archive(temp_dir, dst_file) diff --git a/cvat/apps/dataset_manager/formats/mask.py b/cvat/apps/dataset_manager/formats/mask.py index 492fed38..b1307a9c 100644 --- a/cvat/apps/dataset_manager/formats/mask.py +++ b/cvat/apps/dataset_manager/formats/mask.py @@ -12,6 +12,7 @@ from cvat.apps.dataset_manager.util import make_zip_archive from datumaro.components.project import Dataset from .registry import dm_env, exporter, importer +from .utils import make_colormap @exporter(name='Segmentation mask', ext='ZIP', version='1.1') @@ -21,12 +22,11 @@ def _export(dst_file, task_data, save_images=False): extractor = extractor.transform(envt.get('polygons_to_masks')) extractor = extractor.transform(envt.get('boxes_to_masks')) extractor = extractor.transform(envt.get('merge_instance_segments')) - extractor = extractor.transform(envt.get('id_from_image_name')) extractor = Dataset.from_extractors(extractor) # apply lazy transforms with TemporaryDirectory() as temp_dir: - converter = dm_env.make_converter('voc_segmentation', - apply_colormap=True, label_map='source', save_images=save_images) - converter(extractor, save_dir=temp_dir) + dm_env.converters.get('voc_segmentation').convert(extractor, + save_dir=temp_dir, save_images=save_images, + apply_colormap=True, label_map=make_colormap(task_data)) make_zip_archive(temp_dir, dst_file) diff --git a/cvat/apps/dataset_manager/formats/mot.py b/cvat/apps/dataset_manager/formats/mot.py index 4731284a..b854ddb2 100644 --- a/cvat/apps/dataset_manager/formats/mot.py +++ b/cvat/apps/dataset_manager/formats/mot.py @@ -7,8 +7,7 @@ from tempfile import TemporaryDirectory from pyunpack import Archive import datumaro.components.extractor as datumaro -from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, - match_frame) +from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor from cvat.apps.dataset_manager.util import make_zip_archive from datumaro.components.project import Dataset @@ -18,13 +17,10 @@ from .registry import dm_env, exporter, importer @exporter(name='MOT', ext='ZIP', version='1.1') def _export(dst_file, task_data, save_images=False): extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - envt = dm_env.transforms - extractor = extractor.transform(envt.get('id_from_image_name')) extractor = Dataset.from_extractors(extractor) # apply lazy transforms with TemporaryDirectory() as temp_dir: - converter = dm_env.make_converter('mot_seq_gt', - save_images=save_images) - converter(extractor, save_dir=temp_dir) + dm_env.converters.get('mot_seq_gt').convert(extractor, + save_dir=temp_dir, save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -39,8 +35,8 @@ def _import(src_file, task_data): label_cat = dataset.categories()[datumaro.AnnotationType.label] for item in dataset: - item = item.wrap(id=int(item.id) - 1) # NOTE: MOT frames start from 1 - frame_id = match_frame(item, task_data) + frame_number = int(item.id) - 1 # NOTE: MOT frames start from 1 + frame_number = task_data.abs_frame_id(frame_number) for ann in item.annotations: if ann.type != datumaro.AnnotationType.bbox: @@ -48,6 +44,18 @@ def _import(src_file, task_data): track_id = ann.attributes.get('track_id') if track_id is None: + # Extension. Import regular boxes: + task_data.add_shape(task_data.LabeledShape( + type='rectangle', + label=label_cat.items[ann.label].name, + points=ann.points, + occluded=ann.attributes.get('occluded') == True, + z_order=ann.z_order, + group=0, + frame=frame_number, + attributes=[], + source='manual', + )) continue shape = task_data.TrackedShape( @@ -55,21 +63,27 @@ def _import(src_file, task_data): points=ann.points, occluded=ann.attributes.get('occluded') == True, outside=False, - keyframe=False, + keyframe=True, z_order=ann.z_order, - frame=frame_id, + frame=frame_number, attributes=[], + source='manual', ) # build trajectories as lists of shapes in track dict if track_id not in tracks: tracks[track_id] = task_data.Track( - label_cat.items[ann.label].name, 0, []) + label_cat.items[ann.label].name, 0, 'manual', []) tracks[track_id].shapes.append(shape) for track in tracks.values(): # MOT annotations do not require frames to be ordered track.shapes.sort(key=lambda t: t.frame) - # Set outside=True for the last shape in a track to finish the track - track.shapes[-1] = track.shapes[-1]._replace(outside=True) + # Append a shape with outside=True to finish the track + last_shape = track.shapes[-1] + if last_shape.frame + task_data.frame_step <= \ + int(task_data.meta['task']['stop_frame']): + track.shapes.append(last_shape._replace(outside=True, + frame=last_shape.frame + task_data.frame_step) + ) task_data.add_track(track) diff --git a/cvat/apps/dataset_manager/formats/pascal_voc.py b/cvat/apps/dataset_manager/formats/pascal_voc.py index 429696ba..ee30564b 100644 --- a/cvat/apps/dataset_manager/formats/pascal_voc.py +++ b/cvat/apps/dataset_manager/formats/pascal_voc.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: MIT +import os import os.path as osp import shutil from glob import glob @@ -21,13 +22,10 @@ from .registry import dm_env, exporter, importer @exporter(name='PASCAL VOC', ext='ZIP', version='1.1') def _export(dst_file, task_data, save_images=False): extractor = CvatTaskDataExtractor(task_data, include_images=save_images) - envt = dm_env.transforms - extractor = extractor.transform(envt.get('id_from_image_name')) extractor = Dataset.from_extractors(extractor) # apply lazy transforms with TemporaryDirectory() as temp_dir: - converter = dm_env.make_converter('voc', label_map='source', - save_images=save_images) - converter(extractor, save_dir=temp_dir) + dm_env.converters.get('voc').convert(extractor, + save_dir=temp_dir, save_images=save_images, label_map='source') make_zip_archive(temp_dir, dst_file) diff --git a/cvat/apps/dataset_manager/formats/predefined_colors.txt b/cvat/apps/dataset_manager/formats/predefined_colors.txt new file mode 100644 index 00000000..4aee0d78 --- /dev/null +++ b/cvat/apps/dataset_manager/formats/predefined_colors.txt @@ -0,0 +1,731 @@ +# ImageNet + OpenImages + PASCAL VOC + +# CamVid + CityScapes + Kitti + +# COCO + COCO stuff + +# Format: +# pascal idx : normalized label name : R,G,B + +0:background:0,0,0 +1:accordion:128,0,0 +2:adhesive_tape:0,128,0 +3:aeroplane:128,128,0 +4:aircraft:0,0,128 +5:airplane:128,0,128 +6:alarm_clock:0,128,128 +7:alpaca:128,128,128 +8:ambulance:64,0,0 +9:animal:192,0,0 +10:ant:64,128,0 +11:antelope:192,128,0 +12:apple:64,0,128 +13:armadillo:192,0,128 +14:artichoke:64,128,128 +15:asparagus:192,128,128 +16:axe:0,64,0 +17:baby_bed:128,64,0 +18:backpack:0,192,0 +19:bagel:128,192,0 +20:balance_beam:0,64,128 +21:ball:128,64,128 +22:balloon:0,192,128 +23:banana:128,192,128 +24:band_aid:64,64,0 +25:banjo:192,64,0 +26:banner:64,192,0 +27:barge:192,192,0 +28:barrel:64,64,128 +29:baseball:192,64,128 +30:baseball_bat:64,192,128 +31:baseball_glove:192,192,128 +32:basketball:0,0,64 +33:bat:128,0,64 +34:bathing_cap:0,128,64 +35:bathroom_cabinet:128,128,64 +36:bathtub:0,0,192 +37:beaker:128,0,192 +38:bear:0,128,192 +39:bed:128,128,192 +40:bee:64,0,64 +41:beehive:192,0,64 +42:beer:64,128,64 +43:beetle:192,128,64 +44:bell_pepper:64,0,192 +45:belt:192,0,192 +46:bench:64,128,192 +47:bg:192,128,192 +48:bicycle:0,64,64 +49:bicycle_helmet:128,64,64 +50:bicycle_wheel:0,192,64 +51:bidet:128,192,64 +52:billboard:0,64,192 +53:billiard_table:128,64,192 +54:binder:0,192,192 +55:binoculars:128,192,192 +56:bird:64,64,64 +57:blanket:192,64,64 +58:blender:64,192,64 +59:blue_jay:192,192,64 +60:boat:64,64,192 +61:book:192,64,192 +62:bookcase:64,192,192 +63:bookshelf:192,192,192 +64:boot:32,0,0 +65:bottle:160,0,0 +66:bow:32,128,0 +67:bow_and_arrow:160,128,0 +68:bow_tie:32,0,128 +69:bowl:160,0,128 +70:box:32,128,128 +71:boy:160,128,128 +72:branch:96,0,0 +73:brassiere:224,0,0 +74:bread:96,128,0 +75:bridge:224,128,0 +76:briefcase:96,0,128 +77:broccoli:224,0,128 +78:bronze_sculpture:96,128,128 +79:brown_bear:224,128,128 +80:building:32,64,0 +81:building_other:160,64,0 +82:bull:32,192,0 +83:burrito:160,192,0 +84:bus:32,64,128 +85:bush:160,64,128 +86:bust:32,192,128 +87:butterfly:160,192,128 +88:cabbage:96,64,0 +89:cabinet:224,64,0 +90:cabinetry:96,192,0 +91:cage:224,192,0 +92:cake:96,64,128 +93:cake_stand:224,64,128 +94:camel:96,192,128 +95:camera:224,192,128 +96:can_opener:32,0,64 +97:canary:160,0,64 +98:candle:32,128,64 +99:candy:160,128,64 +100:cannon:32,0,192 +101:canoe:160,0,192 +102:car:32,128,192 +103:caravan:160,128,192 +104:cardboard:96,0,64 +105:carnivore:224,0,64 +106:carpet:96,128,64 +107:carrot:224,128,64 +108:cart:96,0,192 +109:castle:224,0,192 +110:cat:96,128,192 +111:caterpillar:224,128,192 +112:cattle:32,64,64 +113:ceiling_fan:160,64,64 +114:ceiling_other:32,192,64 +115:ceiling_tile:160,192,64 +116:cell_phone:32,64,192 +117:cello:160,64,192 +118:centipede:32,192,192 +119:chain_saw:160,192,192 +120:chair:96,64,64 +121:cheetah:224,64,64 +122:chest_of_drawers:96,192,64 +123:chicken:224,192,64 +124:chime:96,64,192 +125:chopsticks:224,64,192 +126:christmas_tree:96,192,192 +127:clock:224,192,192 +128:cloth:0,32,0 +129:clothes:128,32,0 +130:clouds:0,160,0 +131:coat:128,160,0 +132:cocktail:0,32,128 +133:cocktail_shaker:128,32,128 +134:coconut:0,160,128 +135:coffee:128,160,128 +136:coffee_cup:64,32,0 +137:coffee_maker:192,32,0 +138:coffee_table:64,160,0 +139:coffeemaker:192,160,0 +140:coin:64,32,128 +141:common_fig:192,32,128 +142:computer_keyboard:64,160,128 +143:computer_monitor:192,160,128 +144:computer_mouse:0,96,0 +145:construction:128,96,0 +146:convenience_store:0,224,0 +147:cookie:128,224,0 +148:corded_phone:0,96,128 +149:corkscrew:128,96,128 +150:couch:0,224,128 +151:counter:128,224,128 +152:countertop:64,96,0 +153:cow:192,96,0 +154:cowboy_hat:64,224,0 +155:crab:192,224,0 +156:cream:64,96,128 +157:cricket_ball:192,96,128 +158:crocodile:64,224,128 +159:croissant:192,224,128 +160:croquet_ball:0,32,64 +161:crosswalk:128,32,64 +162:crown:0,160,64 +163:crutch:128,160,64 +164:cucumber:0,32,192 +165:cup:128,32,192 +166:cup_or_mug:0,160,192 +167:cupboard:128,160,192 +168:curtain:64,32,64 +169:cutting_board:192,32,64 +170:cyclist:64,160,64 +171:dagger:192,160,64 +172:deer:64,32,192 +173:desk:192,32,192 +174:desk_stuff:64,160,192 +175:dessert:192,160,192 +176:diaper:0,96,64 +177:dice:128,96,64 +178:digital_clock:0,224,64 +179:dining_table:128,224,64 +180:diningtable:0,96,192 +181:dinosaur:128,96,192 +182:dirt:0,224,192 +183:dishwasher:128,224,192 +184:dog:64,96,64 +185:dog_bed:192,96,64 +186:doll:64,224,64 +187:dolphin:192,224,64 +188:domestic_cat:64,96,192 +189:dontcare:192,96,192 +190:donut:64,224,192 +191:door:192,224,192 +192:door_handle:32,32,0 +193:door_stuff:160,32,0 +194:doughnut:32,160,0 +195:dragonfly:160,160,0 +196:drawer:32,32,128 +197:dress:160,32,128 +198:drink:32,160,128 +199:drinking_straw:160,160,128 +200:drum:96,32,0 +201:duck:224,32,0 +202:dumbbell:96,160,0 +203:dynamic:224,160,0 +204:eagle:96,32,128 +205:earrings:224,32,128 +206:egg:96,160,128 +207:electric_fan:224,160,128 +208:elephant:32,96,0 +209:envelope:160,96,0 +210:eye_glasses:32,224,0 +211:face_powder:160,224,0 +212:falcon:32,96,128 +213:fedora:160,96,128 +214:fence:32,224,128 +215:fence_guard:160,224,128 +216:fig:96,96,0 +217:filing_cabinet:224,96,0 +218:finish:96,224,0 +219:fire_hydrant:224,224,0 +220:fireplace:96,96,128 +221:fish:224,96,128 +222:flag:96,224,128 +223:flashlight:224,224,128 +224:floor_marble:32,32,64 +225:floor_other:160,32,64 +226:floor_stone:32,160,64 +227:floor_tile:160,160,64 +228:floor_wood:32,32,192 +229:flower:160,32,192 +230:flower_pot:32,160,192 +231:flowerpot:160,160,192 +232:flute:96,32,64 +233:fog:224,32,64 +234:food_other:96,160,64 +235:food_processor:224,160,64 +236:football:96,32,192 +237:football_helmet:224,32,192 +238:footwear:96,160,192 +239:fork:224,160,192 +240:fountain:32,96,64 +241:fox:160,96,64 +242:french_fries:32,224,64 +243:french_horn:160,224,64 +244:frisbee:32,96,192 +245:frog:160,96,192 +246:fruit:32,224,192 +247:frying_pan:160,224,192 +248:furniture:96,96,64 +249:furniture_other:224,96,64 +250:gas_stove:96,224,64 +251:giant_panda:224,224,64 +252:giraffe:96,96,192 +253:girl:224,96,192 +254:glasses:96,224,192 +255:glove:224,224,192 +256:goat:0,0,32 +257:goggles:128,0,32 +258:goldfish:0,128,32 +259:golf_ball:128,128,32 +260:golf_cart:0,0,160 +261:golfcart:128,0,160 +262:gondola:0,128,160 +263:goose:128,128,160 +264:grape:64,0,32 +265:grapefruit:192,0,32 +266:grass:64,128,32 +267:gravel:192,128,32 +268:ground:64,0,160 +269:ground_other:192,0,160 +270:guacamole:64,128,160 +271:guitar:192,128,160 +272:hair_brush:0,64,32 +273:hair_drier:128,64,32 +274:hair_dryer:0,192,32 +275:hair_spray:128,192,32 +276:hamburger:0,64,160 +277:hammer:128,64,160 +278:hamster:0,192,160 +279:handbag:128,192,160 +280:handgun:64,64,32 +281:harbor_seal:192,64,32 +282:harmonica:64,192,32 +283:harp:192,192,32 +284:harpsichord:64,64,160 +285:hat:192,64,160 +286:hat_with_a_wide_brim:64,192,160 +287:head_cabbage:192,192,160 +288:headphones:0,0,96 +289:helicopter:128,0,96 +290:helmet:0,128,96 +291:high_heels:128,128,96 +292:hill:0,0,224 +293:hippopotamus:128,0,224 +294:home_appliance:0,128,224 +295:honeycomb:128,128,224 +296:horizontal_bar:64,0,96 +297:horn:192,0,96 +298:horse:64,128,96 +299:hot_dog:192,128,96 +300:hotdog:64,0,224 +301:house:192,0,224 +302:houseplant:64,128,224 +303:human:192,128,224 +304:human_arm:0,64,96 +305:human_beard:128,64,96 +306:human_ear:0,192,96 +307:human_eye:128,192,96 +308:human_face:0,64,224 +309:human_foot:128,64,224 +310:human_hair:0,192,224 +311:human_hand:128,192,224 +312:human_head:64,64,96 +313:human_leg:192,64,96 +314:human_mouth:64,192,96 +315:human_nose:192,192,96 +316:ice_cream:64,64,224 +317:ignore:192,64,224 +318:infant_bed:64,192,224 +319:insect:192,192,224 +320:invertebrate:32,0,32 +321:ipod:160,0,32 +322:isopod:32,128,32 +323:jacket:160,128,32 +324:jaguar:32,0,160 +325:jeans:160,0,160 +326:jellyfish:32,128,160 +327:jet_ski:160,128,160 +328:jug:96,0,32 +329:juice:224,0,32 +330:kangaroo:96,128,32 +331:kettle:224,128,32 +332:keyboard:96,0,160 +333:kitchen_appliance:224,0,160 +334:kitchen_dining_room_table:96,128,160 +335:kitchen_knife:224,128,160 +336:kite:32,64,32 +337:knife:160,64,32 +338:koala_bear:32,192,32 +339:ladder:160,192,32 +340:ladle:32,64,160 +341:ladybug:160,64,160 +342:lamp:32,192,160 +343:land_vehicle:160,192,160 +344:lantern:96,64,32 +345:laptop:224,64,32 +346:lavender:96,192,32 +347:leaves:224,192,32 +348:lemon:96,64,160 +349:leopard:224,64,160 +350:license_plate:96,192,160 +351:lifejacket:224,192,160 +352:light:32,0,96 +353:light_bulb:160,0,96 +354:light_switch:32,128,96 +355:lighthouse:160,128,96 +356:lily:32,0,224 +357:limousine:160,0,224 +358:lion:32,128,224 +359:lipstick:160,128,224 +360:lizard:96,0,96 +361:lobster:224,0,96 +362:loveseat:96,128,96 +363:luggage_and_bags:224,128,96 +364:lynx:96,0,224 +365:maillot:224,0,224 +366:man:96,128,224 +367:mango:224,128,224 +368:maple:32,64,96 +369:maraca:160,64,96 +370:marine_invertebrates:32,192,96 +371:marine_mammal:160,192,96 +372:mat:32,64,224 +373:measuring_cup:160,64,224 +374:mechanical_fan:32,192,224 +375:metal:160,192,224 +376:microphone:96,64,96 +377:microwave:224,64,96 +378:microwave_oven:96,192,96 +379:milk_can:224,192,96 +380:miniskirt:96,64,224 +381:mirror:224,64,224 +382:mirror_stuff:96,192,224 +383:misc:224,192,224 +384:missile:0,32,32 +385:mixer:128,32,32 +386:mobile_phone:0,160,32 +387:monkey:128,160,32 +388:moss:0,32,160 +389:moths_and_butterflies:128,32,160 +390:motorbike:0,160,160 +391:motorcycle:128,160,160 +392:mountain:64,32,32 +393:mouse:192,32,32 +394:mud:64,160,32 +395:muffin:192,160,32 +396:mug:64,32,160 +397:mule:192,32,160 +398:mushroom:64,160,160 +399:musical_instrument:192,160,160 +400:musical_keyboard:0,96,32 +401:nail:128,96,32 +402:napkin:0,224,32 +403:nature:128,224,32 +404:neck_brace:0,96,160 +405:necklace:128,96,160 +406:net:0,224,160 +407:nightstand:128,224,160 +408:non_vehicle:64,96,32 +409:obj:192,96,32 +410:object:64,224,32 +411:oboe:192,224,32 +412:office_building:64,96,160 +413:office_supplies:192,96,160 +414:on_rails:64,224,160 +415:orange:192,224,160 +416:organ:0,32,96 +417:ostrich:128,32,96 +418:otter:0,160,96 +419:oven:128,160,96 +420:owl:0,32,224 +421:oyster:128,32,224 +422:paddle:0,160,224 +423:palm_tree:128,160,224 +424:pancake:64,32,96 +425:paper:192,32,96 +426:paper_towel:64,160,96 +427:parachute:192,160,96 +428:parking:64,32,224 +429:parking_meter:192,32,224 +430:parrot:64,160,224 +431:pasta:192,160,224 +432:pavement:0,96,96 +433:peach:128,96,96 +434:pear:0,224,96 +435:pedestrian:128,224,96 +436:pen:0,96,224 +437:pencil_box:128,96,224 +438:pencil_sharpener:0,224,224 +439:penguin:128,224,224 +440:perfume:64,96,96 +441:person:192,96,96 +442:personal_care:64,224,96 +443:piano:192,224,96 +444:picnic_basket:64,96,224 +445:picture_frame:192,96,224 +446:pig:64,224,224 +447:pillow:192,224,224 +448:pineapple:32,32,32 +449:ping_pong_ball:160,32,32 +450:pitcher:32,160,32 +451:pizza:160,160,32 +452:plant_other:32,32,160 +453:plastic:160,32,160 +454:plastic_bag:32,160,160 +455:plate:160,160,160 +456:plate_rack:96,32,32 +457:platform:224,32,32 +458:platter:96,160,32 +459:playingfield:224,160,32 +460:plumbing_fixture:96,32,160 +461:polar_bear:224,32,160 +462:pole:96,160,160 +463:pole_group:224,160,160 +464:pomegranate:32,96,32 +465:popcorn:160,96,32 +466:popsicle:32,224,32 +467:porch:160,224,32 +468:porcupine:32,96,160 +469:poster:160,96,160 +470:potato:32,224,160 +471:potted_plant:160,224,160 +472:pottedplant:96,96,32 +473:power_drill:224,96,32 +474:power_plugs_and_sockets:96,224,32 +475:pressure_cooker:224,224,32 +476:pretzel:96,96,160 +477:printer:224,96,160 +478:puck:96,224,160 +479:pumpkin:224,224,160 +480:punching_bag:32,32,96 +481:purse:160,32,96 +482:rabbit:32,160,96 +483:raccoon:160,160,96 +484:racket:32,32,224 +485:radish:160,32,224 +486:rail:32,160,224 +487:rail_track:160,160,224 +488:railing:96,32,96 +489:railroad:224,32,96 +490:raven:96,160,96 +491:ray:224,160,96 +492:red_panda:96,32,224 +493:refrigerator:224,32,224 +494:remote:96,160,224 +495:remote_control:224,160,224 +496:reptile:32,96,96 +497:rhinoceros:160,96,96 +498:rider:32,224,96 +499:rifle:160,224,96 +500:ring_binder:32,96,224 +501:river:160,96,224 +502:road:32,224,224 +503:rock:160,224,224 +504:rocket:96,96,96 +505:roller_skates:224,96,96 +506:roof:96,224,96 +507:rose:224,224,96 +508:rubber_eraser:96,96,224 +509:rug:224,96,224 +510:rugby_ball:96,224,224 +511:ruler:224,224,224 +512:salad:16,0,0 +513:salt_and_pepper_shakers:144,0,0 +514:salt_or_pepper_shaker:16,128,0 +515:sand:144,128,0 +516:sandal:16,0,128 +517:sandwich:144,0,128 +518:saucer:16,128,128 +519:saxophone:144,128,128 +520:scarf:80,0,0 +521:scissors:208,0,0 +522:scoreboard:80,128,0 +523:scorpion:208,128,0 +524:screwdriver:80,0,128 +525:sculpture:208,0,128 +526:sea:80,128,128 +527:sea_lion:208,128,128 +528:sea_turtle:16,64,0 +529:seafood:144,64,0 +530:seahorse:16,192,0 +531:seal:144,192,0 +532:seat_belt:16,64,128 +533:segway:144,64,128 +534:serving_tray:16,192,128 +535:sewing_machine:144,192,128 +536:shark:80,64,0 +537:sheep:208,64,0 +538:shelf:80,192,0 +539:shellfish:208,192,0 +540:shirt:80,64,128 +541:shoe:208,64,128 +542:shorts:80,192,128 +543:shotgun:208,192,128 +544:shower:16,0,64 +545:shrimp:144,0,64 +546:sidewalk:16,128,64 +547:sink:144,128,64 +548:skateboard:16,0,192 +549:ski:144,0,192 +550:skirt:16,128,192 +551:skis:144,128,192 +552:skull:80,0,64 +553:skunk:208,0,64 +554:sky:80,128,64 +555:sky_other:208,128,64 +556:skyscraper:80,0,192 +557:slow_cooker:208,0,192 +558:snail:80,128,192 +559:snake:208,128,192 +560:snow:16,64,64 +561:snowboard:144,64,64 +562:snowman:16,192,64 +563:snowmobile:144,192,64 +564:snowplow:16,64,192 +565:soap_dispenser:144,64,192 +566:soccer_ball:16,192,192 +567:sock:144,192,192 +568:sofa:80,64,64 +569:sofa_bed:208,64,64 +570:solid_other:80,192,64 +571:sombrero:208,192,64 +572:sparrow:80,64,192 +573:spatula:208,64,192 +574:spider:80,192,192 +575:spoon:208,192,192 +576:sports_ball:48,0,0 +577:sports_uniform:176,0,0 +578:squash:48,128,0 +579:squirrel:176,128,0 +580:stairs:48,0,128 +581:starfish:176,0,128 +582:start:48,128,128 +583:static:176,128,128 +584:stationary_bicycle:112,0,0 +585:stethoscope:240,0,0 +586:stone:112,128,0 +587:stool:240,128,0 +588:stop:112,0,128 +589:stop_sign:240,0,128 +590:stove:112,128,128 +591:strainer:240,128,128 +592:straw:48,64,0 +593:strawberry:176,64,0 +594:street_light:48,192,0 +595:street_sign:176,192,0 +596:stretcher:48,64,128 +597:structural_other:176,64,128 +598:studio_couch:48,192,128 +599:submarine_sandwich:176,192,128 +600:suit:112,64,0 +601:suitcase:240,64,0 +602:sun_hat:112,192,0 +603:sunflower:240,192,0 +604:sunglasses:112,64,128 +605:surfboard:240,64,128 +606:sushi:112,192,128 +607:swan:240,192,128 +608:swim_cap:48,0,64 +609:swimming_pool:176,0,64 +610:swimming_trunks:48,128,64 +611:swimwear:176,128,64 +612:swine:48,0,192 +613:sword:176,0,192 +614:syringe:48,128,192 +615:table:176,128,192 +616:table_tennis_racket:112,0,64 +617:tablet_computer:240,0,64 +618:tableware:112,128,64 +619:taco:240,128,64 +620:tank:112,0,192 +621:tap:240,0,192 +622:tape_player:112,128,192 +623:tart:240,128,192 +624:taxi:48,64,64 +625:tea:176,64,64 +626:teapot:48,192,64 +627:teddy_bear:176,192,64 +628:telephone:48,64,192 +629:television:176,64,192 +630:tennis_ball:48,192,192 +631:tennis_racket:176,192,192 +632:tent:112,64,64 +633:terrain:240,64,64 +634:textile_other:112,192,64 +635:tiara:240,192,64 +636:tick:112,64,192 +637:tie:240,64,192 +638:tiger:112,192,192 +639:tin_can:240,192,192 +640:tire:16,32,0 +641:toaster:144,32,0 +642:toilet:16,160,0 +643:toilet_paper:144,160,0 +644:tomato:16,32,128 +645:toothbrush:144,32,128 +646:torch:16,160,128 +647:tortoise:144,160,128 +648:towel:80,32,0 +649:tower:208,32,0 +650:toy:80,160,0 +651:traffic_light:208,160,0 +652:traffic_sign:80,32,128 +653:trailer:208,32,128 +654:train:80,160,128 +655:tram:208,160,128 +656:treadmill:16,96,0 +657:tree:144,96,0 +658:tripod:16,224,0 +659:trombone:144,224,0 +660:trousers:16,96,128 +661:truck:144,96,128 +662:trumpet:16,224,128 +663:tunnel:144,224,128 +664:turkey:80,96,0 +665:turtle:208,96,0 +666:tv:80,224,0 +667:tv_or_monitor:208,224,0 +668:tvmonitor:80,96,128 +669:umbrella:208,96,128 +670:undefined:80,224,128 +671:unicycle:208,224,128 +672:unlabeled:16,32,64 +673:vacuum:144,32,64 +674:van:16,160,64 +675:vase:144,160,64 +676:vegetable:16,32,192 +677:vegetation:144,32,192 +678:vehicle:16,160,192 +679:vehicle_registration_plate:144,160,192 +680:violin:80,32,64 +681:void:208,32,64 +682:volleyball:80,160,64 +683:waffle:208,160,64 +684:waffle_iron:80,32,192 +685:wall:208,32,192 +686:wall_brick:80,160,192 +687:wall_clock:208,160,192 +688:wall_concrete:16,96,64 +689:wall_other:144,96,64 +690:wall_panel:16,224,64 +691:wall_stone:144,224,64 +692:wall_tile:16,96,192 +693:wall_wood:144,96,192 +694:washer:16,224,192 +695:washing_machine:144,224,192 +696:waste_container:80,96,64 +697:watch:208,96,64 +698:water_bottle:80,224,64 +699:water_other:208,224,64 +700:watercraft:80,96,192 +701:waterdrops:208,96,192 +702:watermelon:80,224,192 +703:weapon:208,224,192 +704:whale:48,32,0 +705:wheel:176,32,0 +706:wheelchair:48,160,0 +707:whiteboard:176,160,0 +708:willow:48,32,128 +709:window:176,32,128 +710:window_blind:48,160,128 +711:window_other:176,160,128 +712:wine:112,32,0 +713:wine_bottle:240,32,0 +714:wine_glass:112,160,0 +715:winter_melon:240,160,0 +716:wok:112,32,128 +717:woman:240,32,128 +718:wood:112,160,128 +719:wood_burning_stove:240,160,128 +720:woodpecker:48,96,0 +721:wrench:176,96,0 +722:zebra:48,224,0 +723:zucchini:176,224,0 diff --git a/cvat/apps/dataset_manager/formats/registry.py b/cvat/apps/dataset_manager/formats/registry.py index 20377dd6..ed4defc5 100644 --- a/cvat/apps/dataset_manager/formats/registry.py +++ b/cvat/apps/dataset_manager/formats/registry.py @@ -13,6 +13,7 @@ class _Format: EXT = '' VERSION = '' DISPLAY_NAME = '{NAME} {VERSION}' + ENABLED = True class Exporter(_Format): def __call__(self, dst_file, task_data, **options): @@ -22,7 +23,7 @@ class Importer(_Format): def __call__(self, src_file, task_data, **options): raise NotImplementedError() -def _wrap_format(f_or_cls, klass, name, version, ext, display_name): +def _wrap_format(f_or_cls, klass, name, version, ext, display_name, enabled): import inspect assert inspect.isclass(f_or_cls) or inspect.isfunction(f_or_cls) if inspect.isclass(f_or_cls): @@ -44,14 +45,17 @@ def _wrap_format(f_or_cls, klass, name, version, ext, display_name): target.DISPLAY_NAME = (display_name or klass.DISPLAY_NAME).format( NAME=name, VERSION=version, EXT=ext) assert all([target.NAME, target.VERSION, target.EXT, target.DISPLAY_NAME]) + target.ENABLED = enabled + return target EXPORT_FORMATS = {} -def exporter(name, version, ext, display_name=None): +def exporter(name, version, ext, display_name=None, enabled=True): assert name not in EXPORT_FORMATS, "Export format '%s' already registered" % name def wrap_with_params(f_or_cls): t = _wrap_format(f_or_cls, Exporter, - name=name, ext=ext, version=version, display_name=display_name) + name=name, ext=ext, version=version, display_name=display_name, + enabled=enabled) key = t.DISPLAY_NAME assert key not in EXPORT_FORMATS, "Export format '%s' already registered" % name EXPORT_FORMATS[key] = t @@ -59,10 +63,11 @@ def exporter(name, version, ext, display_name=None): return wrap_with_params IMPORT_FORMATS = {} -def importer(name, version, ext, display_name=None): +def importer(name, version, ext, display_name=None, enabled=True): def wrap_with_params(f_or_cls): t = _wrap_format(f_or_cls, Importer, - name=name, ext=ext, version=version, display_name=display_name) + name=name, ext=ext, version=version, display_name=display_name, + enabled=enabled) key = t.DISPLAY_NAME assert key not in IMPORT_FORMATS, "Import format '%s' already registered" % name IMPORT_FORMATS[key] = t diff --git a/cvat/apps/dataset_manager/formats/tfrecord.py b/cvat/apps/dataset_manager/formats/tfrecord.py index 0e4962fa..3b7e123e 100644 --- a/cvat/apps/dataset_manager/formats/tfrecord.py +++ b/cvat/apps/dataset_manager/formats/tfrecord.py @@ -14,18 +14,25 @@ from datumaro.components.project import Dataset from .registry import dm_env, exporter, importer -@exporter(name='TFRecord', ext='ZIP', version='1.0') +from datumaro.util.tf_util import import_tf +try: + import_tf() + tf_available = True +except ImportError: + tf_available = False + + +@exporter(name='TFRecord', ext='ZIP', version='1.0', enabled=tf_available) def _export(dst_file, task_data, save_images=False): extractor = CvatTaskDataExtractor(task_data, include_images=save_images) extractor = Dataset.from_extractors(extractor) # apply lazy transforms with TemporaryDirectory() as temp_dir: - converter = dm_env.make_converter('tf_detection_api', - save_images=save_images) - converter(extractor, save_dir=temp_dir) + dm_env.converters.get('tf_detection_api').convert(extractor, + save_dir=temp_dir, save_images=save_images) make_zip_archive(temp_dir, dst_file) -@importer(name='TFRecord', ext='ZIP', version='1.0') +@importer(name='TFRecord', ext='ZIP', version='1.0', enabled=tf_available) def _import(src_file, task_data): with TemporaryDirectory() as tmp_dir: Archive(src_file.name).extractall(tmp_dir) diff --git a/cvat/apps/dataset_manager/formats/utils.py b/cvat/apps/dataset_manager/formats/utils.py new file mode 100644 index 00000000..2e30ab79 --- /dev/null +++ b/cvat/apps/dataset_manager/formats/utils.py @@ -0,0 +1,79 @@ +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os.path as osp +from pyhash import murmur3_32 + +from datumaro.cli.util import make_file_name + +hasher = murmur3_32() + +def get_color_from_index(index): + def get_bit(number, index): + return (number >> index) & 1 + + color = [0, 0, 0] + + for j in range(7, -1, -1): + for c in range(3): + color[c] |= get_bit(index, c) << j + index >>= 3 + + return tuple(color) + +DEFAULT_COLORMAP_CAPACITY = 2000 +DEFAULT_COLORMAP_PATH = osp.join(osp.dirname(__file__), 'predefined_colors.txt') +def parse_default_colors(file_path=None): + if file_path is None: + file_path = DEFAULT_COLORMAP_PATH + + colors = {} + with open(file_path) as f: + for line in f: + line = line.strip() + if not line or line[0] == '#': + continue + _, label, color = line.split(':') + colors[label] = tuple(map(int, color.split(','))) + return colors + +def normalize_label(label): + label = make_file_name(label) # basically, convert to ASCII lowercase + label = label.replace('-', '_') + return label + +def rgb2hex(color): + return '#{0:02x}{1:02x}{2:02x}'.format(*color) + +def hex2rgb(color): + return tuple(int(color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) + +def make_colormap(task_data): + labels = [label for _, label in task_data.meta['task']['labels']] + label_names = [label['name'] for label in labels] + + if 'background' not in label_names: + labels.insert(0, { + 'name': 'background', + 'color': '#000000', + } + ) + + return {label['name']: [hex2rgb(label['color']), [], []] for label in labels} + + +def get_label_color(label_name, label_names): + predefined = parse_default_colors() + normalized_names = [normalize_label(l_name) for l_name in label_names] + normalized_name = normalize_label(label_name) + + color = predefined.get(normalized_name, None) + offset = hasher(normalized_name) + normalized_names.count(normalized_name) + + if color is None: + color = get_color_from_index(DEFAULT_COLORMAP_CAPACITY + offset) + elif normalized_names.count(normalized_name): + color = get_color_from_index(DEFAULT_COLORMAP_CAPACITY + offset - 1) + + return rgb2hex(color) diff --git a/cvat/apps/dataset_manager/formats/yolo.py b/cvat/apps/dataset_manager/formats/yolo.py index 688ff903..bea73b3c 100644 --- a/cvat/apps/dataset_manager/formats/yolo.py +++ b/cvat/apps/dataset_manager/formats/yolo.py @@ -9,10 +9,11 @@ from tempfile import TemporaryDirectory from pyunpack import Archive from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, - import_dm_annotations, match_frame) + import_dm_annotations, match_dm_item, find_dataset_root) from cvat.apps.dataset_manager.util import make_zip_archive from datumaro.components.extractor import DatasetItem from datumaro.components.project import Dataset +from datumaro.plugins.yolo_format.extractor import YoloExtractor from .registry import dm_env, exporter, importer @@ -22,8 +23,8 @@ def _export(dst_file, task_data, save_images=False): extractor = CvatTaskDataExtractor(task_data, include_images=save_images) extractor = Dataset.from_extractors(extractor) # apply lazy transforms with TemporaryDirectory() as temp_dir: - converter = dm_env.make_converter('yolo', save_images=save_images) - converter(extractor, save_dir=temp_dir) + dm_env.converters.get('yolo').convert(extractor, + save_dir=temp_dir, save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -33,17 +34,20 @@ def _import(src_file, task_data): Archive(src_file.name).extractall(tmp_dir) image_info = {} - anno_files = glob(osp.join(tmp_dir, '**', '*.txt'), recursive=True) - for filename in anno_files: - filename = osp.splitext(osp.basename(filename))[0] + frames = [YoloExtractor.name_from_path(osp.relpath(p, tmp_dir)) + for p in glob(osp.join(tmp_dir, '**', '*.txt'), recursive=True)] + root_hint = find_dataset_root( + [DatasetItem(id=frame) for frame in frames], task_data) + for frame in frames: frame_info = None try: - frame_id = match_frame(DatasetItem(id=filename), task_data) + frame_id = match_dm_item(DatasetItem(id=frame), task_data, + root_hint=root_hint) frame_info = task_data.frame_info[frame_id] except Exception: pass if frame_info is not None: - image_info[filename] = (frame_info['height'], frame_info['width']) + image_info[frame] = (frame_info['height'], frame_info['width']) dataset = dm_env.make_importer('yolo')(tmp_dir, image_info=image_info) \ .make_dataset() diff --git a/cvat/apps/dataset_manager/serializers.py b/cvat/apps/dataset_manager/serializers.py index 51cf71ca..e64c0cb9 100644 --- a/cvat/apps/dataset_manager/serializers.py +++ b/cvat/apps/dataset_manager/serializers.py @@ -9,6 +9,7 @@ class DatasetFormatSerializer(serializers.Serializer): name = serializers.CharField(max_length=64, source='DISPLAY_NAME') ext = serializers.CharField(max_length=64, source='EXT') version = serializers.CharField(max_length=64, source='VERSION') + enabled = serializers.BooleanField(source='ENABLED') class DatasetFormatsSerializer(serializers.Serializer): importers = DatasetFormatSerializer(many=True) diff --git a/cvat/apps/dataset_manager/task.py b/cvat/apps/dataset_manager/task.py index 0d9985b7..246d2ef0 100644 --- a/cvat/apps/dataset_manager/task.py +++ b/cvat/apps/dataset_manager/task.py @@ -381,6 +381,7 @@ class JobAnnotation: 'frame', 'label_id', 'group', + 'source', 'labeledimageattributeval__spec_id', 'labeledimageattributeval__value', 'labeledimageattributeval__id', @@ -415,6 +416,7 @@ class JobAnnotation: 'type', 'frame', 'group', + 'source', 'occluded', 'z_order', 'points', @@ -451,6 +453,7 @@ class JobAnnotation: "frame", "label_id", "group", + "source", "labeledtrackattributeval__spec_id", "labeledtrackattributeval__value", "labeledtrackattributeval__id", @@ -533,6 +536,14 @@ class JobAnnotation: def data(self): return self.ir_data.data + def export(self, dst_file, exporter, host='', **options): + task_data = TaskData( + annotation_ir=self.ir_data, + db_task=self.db_job.segment.task, + host=host, + ) + exporter(dst_file, task_data, **options) + def import_annotations(self, src_file, importer): task_data = TaskData( annotation_ir=AnnotationIR(), @@ -671,6 +682,21 @@ def delete_job_data(pk): annotation = JobAnnotation(pk) annotation.delete() +def export_job(job_id, dst_file, format_name, + server_url=None, save_images=False): + # For big tasks dump function may run for a long time and + # we dont need to acquire lock after the task has been initialized from DB. + # But there is the bug with corrupted dump file in case 2 or + # more dump request received at the same time: + # https://github.com/opencv/cvat/issues/217 + with transaction.atomic(): + job = JobAnnotation(job_id) + job.init_from_db() + + exporter = make_exporter(format_name) + with open(dst_file, 'wb') as f: + job.export(f, exporter, host=server_url, save_images=save_images) + @silk_profile(name="GET task data") @transaction.atomic def get_task_data(pk): @@ -719,8 +745,7 @@ def export_task(task_id, dst_file, format_name, exporter = make_exporter(format_name) with open(dst_file, 'wb') as f: - task.export(f, exporter, host=server_url, - save_images=save_images) + task.export(f, exporter, host=server_url, save_images=save_images) @transaction.atomic def import_task_annotations(task_id, src_file, format_name): diff --git a/cvat/apps/dataset_manager/tests/_test_formats.py b/cvat/apps/dataset_manager/tests/_test_formats.py index bfea13af..3b4ea548 100644 --- a/cvat/apps/dataset_manager/tests/_test_formats.py +++ b/cvat/apps/dataset_manager/tests/_test_formats.py @@ -62,7 +62,6 @@ def _setUpModule(): from io import BytesIO import os.path as osp -import random import tempfile import zipfile @@ -71,28 +70,26 @@ from django.contrib.auth.models import User, Group from rest_framework.test import APITestCase, APIClient from rest_framework import status +from cvat.apps.dataset_manager.annotation import AnnotationIR +from cvat.apps.dataset_manager.bindings import TaskData, find_dataset_root +from cvat.apps.engine.models import Task + _setUpModule() +from cvat.apps.dataset_manager.annotation import AnnotationIR +from cvat.apps.dataset_manager.bindings import TaskData, CvatTaskDataExtractor +from cvat.apps.dataset_manager.task import TaskAnnotation +from cvat.apps.engine.models import Task + -def generate_image_file(filename): +def generate_image_file(filename, size=(100, 50)): f = BytesIO() - width = random.randint(10, 200) - height = random.randint(10, 200) - image = Image.new('RGB', size=(width, height)) + image = Image.new('RGB', size=size) image.save(f, 'jpeg') f.name = filename f.seek(0) - return f -def create_db_users(cls): - group_user, _ = Group.objects.get_or_create(name="user") - - user_dummy = User.objects.create_superuser(username="test", password="test", email="") - user_dummy.groups.add(group_user) - - cls.user = user_dummy - class ForceLogin: def __init__(self, user, client): self.user = user @@ -109,14 +106,47 @@ class ForceLogin: if self.user: self.client.logout() -class TaskExportTest(APITestCase): +class _DbTestBase(APITestCase): def setUp(self): self.client = APIClient() @classmethod def setUpTestData(cls): - create_db_users(cls) + cls.create_db_users() + + @classmethod + def create_db_users(cls): + group, _ = Group.objects.get_or_create(name="adm") + + admin = User.objects.create_superuser( + username="test", password="test", email="") + admin.groups.add(group) + + cls.user = admin + + def _put_api_v1_task_id_annotations(self, tid, data): + with ForceLogin(self.user, self.client): + response = self.client.put("/api/v1/tasks/%s/annotations" % tid, + data=data, format="json") + + return response + + def _create_task(self, data, image_data): + with ForceLogin(self.user, self.client): + response = self.client.post('/api/v1/tasks', data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED, response.status_code + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=image_data) + assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code + + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + return task + +class TaskExportTest(_DbTestBase): def _generate_annotations(self, task): annotations = { "version": 0, @@ -133,6 +163,7 @@ class TaskExportTest(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -151,6 +182,7 @@ class TaskExportTest(APITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 300.222, 400, 500, 1, 3], "type": "polygon", @@ -160,6 +192,7 @@ class TaskExportTest(APITestCase): "frame": 1, "label_id": task["labels"][0]["id"], "group": 1, + "source": "manual", "attributes": [], "points": [100, 300.222, 400, 500, 1, 3], "type": "points", @@ -169,6 +202,7 @@ class TaskExportTest(APITestCase): "frame": 1, "label_id": task["labels"][0]["id"], "group": 1, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 400, 500, 1, 3], "type": "polyline", @@ -180,6 +214,7 @@ class TaskExportTest(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -214,6 +249,7 @@ class TaskExportTest(APITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -231,7 +267,15 @@ class TaskExportTest(APITestCase): self._put_api_v1_task_id_annotations(task["id"], annotations) return annotations - def _generate_task(self): + def _generate_task_images(self, count): # pylint: disable=no-self-use + images = { + "client_files[%d]" % i: generate_image_file("image_%d.jpg" % i) + for i in range(count) + } + images["image_quality"] = 75 + return images + + def _generate_task(self, images): task = { "name": "my task #1", "owner": '', @@ -261,35 +305,10 @@ class TaskExportTest(APITestCase): {"name": "person"}, ] } - return self._create_task(task, 3) - - def _create_task(self, data, size): - with ForceLogin(self.user, self.client): - response = self.client.post('/api/v1/tasks', data=data, format="json") - assert response.status_code == status.HTTP_201_CREATED, response.status_code - tid = response.data["id"] - - images = { - "client_files[%d]" % i: generate_image_file("image_%d.jpg" % i) - for i in range(size) - } - images["image_quality"] = 75 - response = self.client.post("/api/v1/tasks/{}/data".format(tid), data=images) - assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code - - response = self.client.get("/api/v1/tasks/{}".format(tid)) - task = response.data - - return task + return self._create_task(task, images) - def _put_api_v1_task_id_annotations(self, tid, data): - with ForceLogin(self.user, self.client): - response = self.client.put("/api/v1/tasks/{}/annotations".format(tid), - data=data, format="json") - - return response - - def _test_export(self, check, task, format_name, **export_args): + @staticmethod + def _test_export(check, task, format_name, **export_args): with tempfile.TemporaryDirectory() as temp_dir: file_path = osp.join(temp_dir, format_name) dm.task.export_task(task["id"], file_path, @@ -335,11 +354,15 @@ class TaskExportTest(APITestCase): self.assertTrue(len(f.read()) != 0) for f in dm.views.get_export_formats(): + if not f.ENABLED: + self.skipTest("Format is disabled") + format_name = f.DISPLAY_NAME for save_images in { True, False }: + images = self._generate_task_images(3) + task = self._generate_task(images) + self._generate_annotations(task) with self.subTest(format=format_name, save_images=save_images): - task = self._generate_task() - self._generate_annotations(task) self._test_export(check, task, format_name, save_images=save_images) @@ -359,7 +382,11 @@ class TaskExportTest(APITestCase): ('YOLO 1.1', 'yolo'), ]: with self.subTest(format=format_name): - task = self._generate_task() + if not dm.formats.registry.EXPORT_FORMATS[format_name].ENABLED: + self.skipTest("Format is disabled") + + images = self._generate_task_images(3) + task = self._generate_task(images) def check(file_path): def load_dataset(src): @@ -369,6 +396,7 @@ class TaskExportTest(APITestCase): # NOTE: can't import cvat.utils.cli # for whatever reason, so remove the dependency + # project.config.remove('sources') return project.make_dataset() @@ -385,3 +413,148 @@ class TaskExportTest(APITestCase): self.assertEqual(len(dataset), task["size"]) self._test_export(check, task, format_name, save_images=False) + def test_can_skip_outside(self): + images = self._generate_task_images(3) + task = self._generate_task(images) + self._generate_annotations(task) + task_ann = TaskAnnotation(task["id"]) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task["id"])) + + extractor = CvatTaskDataExtractor(task_data, include_outside=False) + dm_dataset = datumaro.components.project.Dataset.from_extractors(extractor) + self.assertEqual(4, len(dm_dataset.get("image_1").annotations)) + + extractor = CvatTaskDataExtractor(task_data, include_outside=True) + dm_dataset = datumaro.components.project.Dataset.from_extractors(extractor) + self.assertEqual(5, len(dm_dataset.get("image_1").annotations)) + + def test_cant_make_rel_frame_id_from_unknown(self): + images = self._generate_task_images(3) + images['frame_filter'] = 'step=2' + task = self._generate_task(images) + task_data = TaskData(AnnotationIR(), Task.objects.get(pk=task['id'])) + + with self.assertRaisesRegex(ValueError, r'Unknown'): + task_data.rel_frame_id(1) # the task has only 0 and 2 frames + + def test_can_make_rel_frame_id_from_known(self): + images = self._generate_task_images(6) + images['frame_filter'] = 'step=2' + images['start_frame'] = 1 + task = self._generate_task(images) + task_data = TaskData(AnnotationIR(), Task.objects.get(pk=task['id'])) + + self.assertEqual(2, task_data.rel_frame_id(5)) + + def test_cant_make_abs_frame_id_from_unknown(self): + images = self._generate_task_images(3) + images['frame_filter'] = 'step=2' + task = self._generate_task(images) + task_data = TaskData(AnnotationIR(), Task.objects.get(pk=task['id'])) + + with self.assertRaisesRegex(ValueError, r'Unknown'): + task_data.abs_frame_id(2) # the task has only 0 and 1 indices + + def test_can_make_abs_frame_id_from_known(self): + images = self._generate_task_images(6) + images['frame_filter'] = 'step=2' + images['start_frame'] = 1 + task = self._generate_task(images) + task_data = TaskData(AnnotationIR(), Task.objects.get(pk=task['id'])) + + self.assertEqual(5, task_data.abs_frame_id(2)) + +class FrameMatchingTest(_DbTestBase): + def _generate_task_images(self, paths): # pylint: disable=no-self-use + f = BytesIO() + with zipfile.ZipFile(f, 'w') as archive: + for path in paths: + archive.writestr(path, generate_image_file(path).getvalue()) + f.name = 'images.zip' + f.seek(0) + + return { + 'client_files[0]': f, + 'image_quality': 75, + } + + def _generate_task(self, images): + task = { + "name": "my task #1", + "owner": '', + "assignee": '', + "overlap": 0, + "segment_size": 100, + "z_order": False, + "labels": [ + { + "name": "car", + "attributes": [ + { + "name": "model", + "mutable": False, + "input_type": "select", + "default_value": "mazda", + "values": ["bmw", "mazda", "renault"] + }, + { + "name": "parked", + "mutable": True, + "input_type": "checkbox", + "default_value": False + }, + ] + }, + {"name": "person"}, + ] + } + return self._create_task(task, images) + + def test_frame_matching(self): + task_paths = [ + 'a.jpg', + 'a/a.jpg', + 'a/b.jpg', + 'b/a.jpg', + 'b/c.jpg', + 'a/b/c.jpg', + 'a/b/d.jpg', + ] + + images = self._generate_task_images(task_paths) + task = self._generate_task(images) + task_data = TaskData(AnnotationIR(), Task.objects.get(pk=task["id"])) + + for input_path, expected, root in [ + ('z.jpg', None, ''), # unknown item + ('z/a.jpg', None, ''), # unknown item + + ('d.jpg', 'a/b/d.jpg', 'a/b'), # match with root hint + ('b/d.jpg', 'a/b/d.jpg', 'a'), # match with root hint + ] + list(zip(task_paths, task_paths, [None] * len(task_paths))): # exact matches + with self.subTest(input=input_path): + actual = task_data.match_frame(input_path, root) + if actual is not None: + actual = task_data.frame_info[actual]['path'] + self.assertEqual(expected, actual) + + def test_dataset_root(self): + for task_paths, dataset_paths, expected in [ + ([ 'a.jpg', 'b/c/a.jpg' ], [ 'a.jpg', 'b/c/a.jpg' ], ''), + ([ 'b/a.jpg', 'b/c/a.jpg' ], [ 'a.jpg', 'c/a.jpg' ], 'b'), # 'images from share' case + ([ 'b/c/a.jpg' ], [ 'a.jpg' ], 'b/c'), # 'images from share' case + ([ 'a.jpg' ], [ 'z.jpg' ], None), + ]: + with self.subTest(expected=expected): + images = self._generate_task_images(task_paths) + task = self._generate_task(images) + task_data = TaskData(AnnotationIR(), + Task.objects.get(pk=task["id"])) + dataset = [ + datumaro.components.extractor.DatasetItem( + id=osp.splitext(p)[0]) + for p in dataset_paths] + + root = find_dataset_root(dataset, task_data) + self.assertEqual(expected, root) diff --git a/cvat/apps/dataset_manager/tests/test_annotation.py b/cvat/apps/dataset_manager/tests/test_annotation.py index 2db39699..7604a4dc 100644 --- a/cvat/apps/dataset_manager/tests/test_annotation.py +++ b/cvat/apps/dataset_manager/tests/test_annotation.py @@ -8,11 +8,12 @@ from unittest import TestCase class TrackManagerTest(TestCase): - def test_single_point_interpolation(self): + def test_point_interpolation(self): track = { "frame": 0, "label_id": 0, "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -36,4 +37,105 @@ class TrackManagerTest(TestCase): interpolated = TrackManager.get_interpolated_shapes(track, 0, 2) - self.assertEqual(len(interpolated), 3) \ No newline at end of file + self.assertEqual(len(interpolated), 3) + self.assertTrue(interpolated[0]["keyframe"]) + self.assertFalse(interpolated[1]["keyframe"]) + + def test_polygon_interpolation(self): + track = { + "frame": 0, + "label_id": 0, + "group": None, + "attributes": [], + "source": "manual", + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.0, 3.0, 4.0, 5.0, 2.0], + "type": "polygon", + "occluded": False, + "outside": False, + "attributes": [] + }, + { + "frame": 2, + "attributes": [], + "points": [3.0, 4.0, 5.0, 6.0, 7.0, 6.0, 4.0, 5.0], + "type": "polygon", + "occluded": False, + "outside": True + }, + ] + } + + interpolated = TrackManager.get_interpolated_shapes(track, 0, 2) + + self.assertEqual(len(interpolated), 3) + self.assertTrue(interpolated[0]["keyframe"]) + self.assertFalse(interpolated[1]["keyframe"]) + + def test_bbox_interpolation(self): + track = { + "frame": 0, + "label_id": 0, + "group": None, + "attributes": [], + "source": "manual", + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.0, 3.0, 4.0], + "type": "rectangle", + "occluded": False, + "outside": False, + "attributes": [] + }, + { + "frame": 2, + "attributes": [], + "points": [3.0, 4.0, 5.0, 6.0], + "type": "rectangle", + "occluded": False, + "outside": True + }, + ] + } + + interpolated = TrackManager.get_interpolated_shapes(track, 0, 2) + + self.assertEqual(len(interpolated), 3) + self.assertTrue(interpolated[0]["keyframe"]) + self.assertFalse(interpolated[1]["keyframe"]) + + def test_line_interpolation(self): + track = { + "frame": 0, + "label_id": 0, + "group": None, + "attributes": [], + "source": "manual", + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], + "type": "polyline", + "occluded": False, + "outside": False, + "attributes": [] + }, + { + "frame": 2, + "attributes": [], + "points": [3.0, 4.0, 5.0, 6.0], + "type": "polyline", + "occluded": False, + "outside": True + }, + ] + } + + interpolated = TrackManager.get_interpolated_shapes(track, 0, 2) + + self.assertEqual(len(interpolated), 3) + self.assertTrue(interpolated[0]["keyframe"]) + self.assertFalse(interpolated[1]["keyframe"]) \ No newline at end of file diff --git a/cvat/apps/dataset_manager/views.py b/cvat/apps/dataset_manager/views.py index c6e5b554..b622eaa6 100644 --- a/cvat/apps/dataset_manager/views.py +++ b/cvat/apps/dataset_manager/views.py @@ -47,7 +47,7 @@ def export_task(task_id, dst_format, server_url=None, save_images=False): cache_dir = get_export_cache_dir(db_task) exporter = EXPORT_FORMATS[dst_format] - output_base = '%s_%s' % ('dataset' if save_images else 'task', + output_base = '%s_%s' % ('dataset' if save_images else 'annotations', make_file_name(to_snake_case(dst_format))) output_path = '%s.%s' % (output_base, exporter.EXT) output_path = osp.join(cache_dir, output_path) diff --git a/cvat/apps/dextr_segmentation/README.md b/cvat/apps/dextr_segmentation/README.md deleted file mode 100644 index 2382a380..00000000 --- a/cvat/apps/dextr_segmentation/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Semi-Automatic Segmentation with [Deep Extreme Cut](http://www.vision.ee.ethz.ch/~cvlsegmentation/dextr/) - -## About the application - -The application allows to use deep learning models for semi-automatic semantic and instance segmentation. -You can get a segmentation polygon from four (or more) extreme points of an object. -This application uses the pre-trained DEXTR model which has been converted to Inference Engine format. - -We are grateful to K.K. Maninis, S. Caelles, J. Pont-Tuset, and L. Van Gool who permitted using their models in our tool - -## Build docker image -```bash -# OpenVINO component is also needed -docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml -f cvat/apps/dextr_segmentation/docker-compose.dextr.yml build -``` - -## Run docker container -```bash -docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml -f cvat/apps/dextr_segmentation/docker-compose.dextr.yml up -d -``` - -## Using - -1. Open a job -2. Select "Auto Segmentation" in the list of shapes -3. Run the draw mode as usually (by press the "Create Shape" button or by "N" shortcut) -4. Click four-six (or more if it's need) extreme points of an object -5. Close the draw mode as usually (by shortcut or pressing the button "Stop Creation") -6. Wait a moment and you will get a class agnostic annotation polygon -7. You can close an annotation request if it is too long -(in case if it is queued to rq worker and all workers are busy) diff --git a/cvat/apps/dextr_segmentation/__init__.py b/cvat/apps/dextr_segmentation/__init__.py deleted file mode 100644 index 472c2ac3..00000000 --- a/cvat/apps/dextr_segmentation/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -JS_3RDPARTY['engine'] = JS_3RDPARTY.get('engine', []) + ['dextr_segmentation/js/enginePlugin.js'] diff --git a/cvat/apps/dextr_segmentation/apps.py b/cvat/apps/dextr_segmentation/apps.py deleted file mode 100644 index d5d43a88..00000000 --- a/cvat/apps/dextr_segmentation/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - -class DextrSegmentationConfig(AppConfig): - name = 'dextr_segmentation' diff --git a/cvat/apps/dextr_segmentation/dextr.py b/cvat/apps/dextr_segmentation/dextr.py deleted file mode 100644 index d6eb2002..00000000 --- a/cvat/apps/dextr_segmentation/dextr.py +++ /dev/null @@ -1,119 +0,0 @@ - -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network -from cvat.apps.engine.frame_provider import FrameProvider - -import os -import cv2 -import PIL -import numpy as np - -_IE_CPU_EXTENSION = os.getenv("IE_CPU_EXTENSION", "libcpu_extension_avx2.so") -_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) - -_DEXTR_MODEL_DIR = os.getenv("DEXTR_MODEL_DIR", None) -_DEXTR_PADDING = 50 -_DEXTR_TRESHOLD = 0.9 -_DEXTR_SIZE = 512 - -class DEXTR_HANDLER: - def __init__(self): - self._plugin = None - self._network = None - self._exec_network = None - self._input_blob = None - self._output_blob = None - if not _DEXTR_MODEL_DIR: - raise Exception("DEXTR_MODEL_DIR is not defined") - - - def handle(self, db_data, frame, points): - # Lazy initialization - if not self._plugin: - self._plugin = make_plugin_or_core() - self._network = make_network(os.path.join(_DEXTR_MODEL_DIR, 'dextr.xml'), - os.path.join(_DEXTR_MODEL_DIR, 'dextr.bin')) - self._input_blob = next(iter(self._network.inputs)) - self._output_blob = next(iter(self._network.outputs)) - if getattr(self._plugin, 'load_network', False): - self._exec_network = self._plugin.load_network(self._network, 'CPU') - else: - self._exec_network = self._plugin.load(network=self._network) - - frame_provider = FrameProvider(db_data) - image = frame_provider.get_frame(frame, frame_provider.Quality.ORIGINAL) - image = PIL.Image.open(image[0]) - numpy_image = np.array(image) - points = np.asarray([[int(p["x"]), int(p["y"])] for p in points], dtype=int) - - # Padding mustn't be more than the closest distance to an edge of an image - [height, width] = numpy_image.shape[:2] - x_values = points[:, 0] - y_values = points[:, 1] - [min_x, max_x] = [np.min(x_values), np.max(x_values)] - [min_y, max_y] = [np.min(y_values), np.max(y_values)] - padding = min(min_x, min_y, width - max_x, height - max_y, _DEXTR_PADDING) - bounding_box = ( - max(min(points[:, 0]) - padding, 0), - max(min(points[:, 1]) - padding, 0), - min(max(points[:, 0]) + padding, width - 1), - min(max(points[:, 1]) + padding, height - 1) - ) - - # Prepare an image - numpy_cropped = np.array(image.crop(bounding_box)) - resized = cv2.resize(numpy_cropped, (_DEXTR_SIZE, _DEXTR_SIZE), - interpolation = cv2.INTER_CUBIC).astype(np.float32) - - # Make a heatmap - points = points - [min(points[:, 0]), min(points[:, 1])] + [padding, padding] - points = (points * [_DEXTR_SIZE / numpy_cropped.shape[1], _DEXTR_SIZE / numpy_cropped.shape[0]]).astype(int) - heatmap = np.zeros(shape=resized.shape[:2], dtype=np.float64) - for point in points: - gaussian_x_axis = np.arange(0, _DEXTR_SIZE, 1, float) - point[0] - gaussian_y_axis = np.arange(0, _DEXTR_SIZE, 1, float)[:, np.newaxis] - point[1] - gaussian = np.exp(-4 * np.log(2) * ((gaussian_x_axis ** 2 + gaussian_y_axis ** 2) / 100)).astype(np.float64) - heatmap = np.maximum(heatmap, gaussian) - cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX) - - # Concat an image and a heatmap - input_dextr = np.concatenate((resized, heatmap[:, :, np.newaxis].astype(resized.dtype)), axis=2) - input_dextr = input_dextr.transpose((2,0,1)) - - pred = self._exec_network.infer(inputs={self._input_blob: input_dextr[np.newaxis, ...]})[self._output_blob][0, 0, :, :] - pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_CUBIC) - result = np.zeros(numpy_image.shape[:2]) - result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred > _DEXTR_TRESHOLD - - # Convert a mask to a polygon - result = np.array(result, dtype=np.uint8) - cv2.normalize(result,result,0,255,cv2.NORM_MINMAX) - contours = None - if int(cv2.__version__.split('.')[0]) > 3: - contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[0] - else: - contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[1] - - contours = max(contours, key=lambda arr: arr.size) - if contours.shape.count(1): - contours = np.squeeze(contours) - if contours.size < 3 * 2: - raise Exception('Less then three point have been detected. Can not build a polygon.') - - result = "" - for point in contours: - result += "{},{} ".format(int(point[0]), int(point[1])) - result = result[:-1] - - return result - - def __del__(self): - if self._exec_network: - del self._exec_network - if self._network: - del self._network - if self._plugin: - del self._plugin diff --git a/cvat/apps/dextr_segmentation/docker-compose.dextr.yml b/cvat/apps/dextr_segmentation/docker-compose.dextr.yml deleted file mode 100644 index 468523b8..00000000 --- a/cvat/apps/dextr_segmentation/docker-compose.dextr.yml +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# - -version: "2.3" - -services: - cvat: - build: - context: . - args: - WITH_DEXTR: "yes" diff --git a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js b/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js deleted file mode 100644 index 0869ba8d..00000000 --- a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global - AREA_TRESHOLD:false - PolyShapeModel:false - ShapeCreatorModel:true - ShapeCreatorView:true - showMessage:false -*/ - -/* eslint no-underscore-dangle: 0 */ - -window.addEventListener('DOMContentLoaded', () => { - $('').appendTo('#shapeTypeSelector'); - - const dextrCancelButtonId = 'dextrCancelButton'; - const dextrOverlay = $(` - `).appendTo('body'); - - const dextrCancelButton = $(`#${dextrCancelButtonId}`); - dextrCancelButton.on('click', () => { - dextrCancelButton.prop('disabled', true); - $.ajax({ - url: `/dextr/cancel/${window.cvat.job.id}`, - type: 'GET', - error: (errorData) => { - const message = `Can not cancel segmentation. Code: ${errorData.status}. - Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }, - complete: () => { - dextrCancelButton.prop('disabled', false); - }, - }); - }); - - function ShapeCreatorModelWrapper(OriginalClass) { - // Constructor will patch some properties for a created instance - function constructorDecorator(...args) { - const instance = new OriginalClass(...args); - - // Decorator for the defaultType property - Object.defineProperty(instance, 'defaultType', { - get: () => instance._defaultType, - set: (type) => { - if (!['box', 'box_by_4_points', 'points', 'polygon', - 'polyline', 'auto_segmentation', 'cuboid'].includes(type)) { - throw Error(`Unknown shape type found ${type}`); - } - instance._defaultType = type; - }, - }); - - // Decorator for finish method. - const decoratedFinish = instance.finish; - instance.finish = (result) => { - if (instance._defaultType === 'auto_segmentation') { - try { - instance._defaultType = 'polygon'; - decoratedFinish.call(instance, result); - } finally { - instance._defaultType = 'auto_segmentation'; - } - } else { - decoratedFinish.call(instance, result); - } - }; - - return instance; - } - - constructorDecorator.prototype = OriginalClass.prototype; - constructorDecorator.prototype.constructor = constructorDecorator; - return constructorDecorator; - } - - - function ShapeCreatorViewWrapper(OriginalClass) { - // Constructor will patch some properties for each instance - function constructorDecorator(...args) { - const instance = new OriginalClass(...args); - - // Decorator for the _create() method. - // We save the decorated _create() and we will use it if type != 'auto_segmentation' - const decoratedCreate = instance._create; - instance._create = () => { - if (instance._type !== 'auto_segmentation') { - decoratedCreate.call(instance); - return; - } - - instance._drawInstance = instance._frameContent.polyline().draw({ snapToGrid: 0.1 }).addClass('shapeCreation').attr({ - 'stroke-width': 0, - z_order: Number.MAX_SAFE_INTEGER, - }); - instance._createPolyEvents(); - - /* the _createPolyEvents method have added "drawdone" - * event handler which invalid for this case - * because of that reason we remove the handler and - * create the valid handler instead - */ - instance._drawInstance.off('drawdone').on('drawdone', (e) => { - let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute('points')); - actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints); - - if (actualPoints.length < 4) { - showMessage('It is need to specify minimum four extreme points for an object'); - instance._controller.switchCreateMode(true); - return; - } - - const { frameWidth } = window.cvat.player.geometry; - const { frameHeight } = window.cvat.player.geometry; - for (let idx = 0; idx < actualPoints.length; idx += 1) { - const point = actualPoints[idx]; - point.x = Math.clamp(point.x, 0, frameWidth); - point.y = Math.clamp(point.y, 0, frameHeight); - } - - e.target.setAttribute('points', - window.cvat.translate.points.actualToCanvas( - PolyShapeModel.convertNumberArrayToString(actualPoints), - )); - - const polybox = e.target.getBBox(); - const area = polybox.width * polybox.height; - - if (area > AREA_TRESHOLD) { - $.ajax({ - url: `/dextr/create/${window.cvat.job.id}`, - type: 'POST', - data: JSON.stringify({ - frame: window.cvat.player.frames.current, - points: actualPoints, - }), - contentType: 'application/json', - success: () => { - function intervalCallback() { - $.ajax({ - url: `/dextr/check/${window.cvat.job.id}`, - type: 'GET', - success: (jobData) => { - if (['queued', 'started'].includes(jobData.status)) { - if (jobData.status === 'queued') { - dextrCancelButton.prop('disabled', false); - } - setTimeout(intervalCallback, 1000); - } else { - dextrOverlay.addClass('hidden'); - if (jobData.status === 'finished') { - if (jobData.result) { - instance._controller.finish({ points: jobData.result }, 'polygon'); - } - } else if (jobData.status === 'failed') { - const message = `Segmentation has fallen. Error: '${jobData.stderr}'`; - showMessage(message); - } else { - let message = `Check segmentation request returned "${jobData.status}" status.`; - if (jobData.stderr) { - message += ` Error: ${jobData.stderr}`; - } - showMessage(message); - } - } - }, - error: (errorData) => { - dextrOverlay.addClass('hidden'); - const message = `Can not check segmentation. Code: ${errorData.status}.` - + ` Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }, - }); - } - - dextrCancelButton.prop('disabled', true); - dextrOverlay.removeClass('hidden'); - setTimeout(intervalCallback, 1000); - }, - error: (errorData) => { - const message = `Can not cancel ReID process. Code: ${errorData.status}.` - + ` Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }, - }); - } - - instance._controller.switchCreateMode(true); - }); // end of "drawdone" handler - }; // end of _create() method - - return instance; - } // end of constructorDecorator() - - constructorDecorator.prototype = OriginalClass.prototype; - constructorDecorator.prototype.constructor = constructorDecorator; - return constructorDecorator; - } // end of ShapeCreatorViewWrapper - - // Apply patch for classes - ShapeCreatorModel = ShapeCreatorModelWrapper(ShapeCreatorModel); - ShapeCreatorView = ShapeCreatorViewWrapper(ShapeCreatorView); -}); diff --git a/cvat/apps/dextr_segmentation/urls.py b/cvat/apps/dextr_segmentation/urls.py deleted file mode 100644 index 6b3120b6..00000000 --- a/cvat/apps/dextr_segmentation/urls.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('create/', views.create), - path('cancel/', views.cancel), - path('check/', views.check), - path('enabled', views.enabled) -] diff --git a/cvat/apps/dextr_segmentation/views.py b/cvat/apps/dextr_segmentation/views.py deleted file mode 100644 index dd78a2b0..00000000 --- a/cvat/apps/dextr_segmentation/views.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse -from cvat.apps.authentication.decorators import login_required -from rules.contrib.views import permission_required, objectgetter - -from cvat.apps.engine.models import Job -from cvat.apps.engine.log import slogger -from cvat.apps.dextr_segmentation.dextr import DEXTR_HANDLER - -import django_rq -import json -import rq - -__RQ_QUEUE_NAME = "default" -__DEXTR_HANDLER = DEXTR_HANDLER() - -def _dextr_thread(db_data, frame, points): - job = rq.get_current_job() - job.meta["result"] = __DEXTR_HANDLER.handle(db_data, frame, points) - job.save_meta() - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, "jid"), raise_exception=True) -def create(request, jid): - try: - data = json.loads(request.body.decode("utf-8")) - - points = data["points"] - frame = int(data["frame"]) - username = request.user.username - - slogger.job[jid].info("create dextr request for the JOB: {} ".format(jid) - + "by the USER: {} on the FRAME: {}".format(username, frame)) - - db_data = Job.objects.select_related("segment__task__data").get(id=jid).segment.task.data - - queue = django_rq.get_queue(__RQ_QUEUE_NAME) - rq_id = "dextr.create/{}/{}".format(jid, username) - job = queue.fetch_job(rq_id) - - if job is not None and (job.is_started or job.is_queued): - if "cancel" not in job.meta: - raise Exception("Segmentation process has been already run for the " + - "JOB: {} and the USER: {}".format(jid, username)) - else: - job.delete() - - queue.enqueue_call(func=_dextr_thread, - args=(db_data, frame, points), - job_id=rq_id, - timeout=15, - ttl=30) - - return HttpResponse() - except Exception as ex: - slogger.job[jid].error("can't create a dextr request for the job {}".format(jid), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, "jid"), raise_exception=True) -def cancel(request, jid): - try: - username = request.user.username - slogger.job[jid].info("cancel dextr request for the JOB: {} ".format(jid) - + "by the USER: {}".format(username)) - - queue = django_rq.get_queue(__RQ_QUEUE_NAME) - rq_id = "dextr.create/{}/{}".format(jid, username) - job = queue.fetch_job(rq_id) - - if job is None or job.is_finished or job.is_failed: - raise Exception("Segmentation isn't running now") - elif "cancel" not in job.meta: - job.meta["cancel"] = True - job.save_meta() - - return HttpResponse() - except Exception as ex: - slogger.job[jid].error("can't cancel a dextr request for the job {}".format(jid), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, "jid"), raise_exception=True) -def check(request, jid): - try: - username = request.user.username - slogger.job[jid].info("check dextr request for the JOB: {} ".format(jid) - + "by the USER: {}".format(username)) - - queue = django_rq.get_queue(__RQ_QUEUE_NAME) - rq_id = "dextr.create/{}/{}".format(jid, username) - job = queue.fetch_job(rq_id) - data = {} - - if job is None: - data["status"] = "unknown" - else: - if "cancel" in job.meta: - data["status"] = "finished" - elif job.is_queued: - data["status"] = "queued" - elif job.is_started: - data["status"] = "started" - elif job.is_finished: - data["status"] = "finished" - data["result"] = job.meta["result"] - job.delete() - else: - data["status"] = "failed" - data["stderr"] = job.exc_info - job.delete() - - return JsonResponse(data) - except Exception as ex: - slogger.job[jid].error("can't check a dextr request for the job {}".format(jid), exc_info=True) - return HttpResponseBadRequest(str(ex)) - -def enabled(request): - return HttpResponse() diff --git a/cvat/apps/documentation/admin.py b/cvat/apps/documentation/admin.py deleted file mode 100644 index af8dfc47..00000000 --- a/cvat/apps/documentation/admin.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.contrib import admin - -# Register your models here. - diff --git a/cvat/apps/documentation/faq.md b/cvat/apps/documentation/faq.md new file mode 100644 index 00000000..396c9ab4 --- /dev/null +++ b/cvat/apps/documentation/faq.md @@ -0,0 +1,110 @@ +# Frequently asked questions +- [How to update CVAT](#how-to-update-cvat) +- [Kibana app works, but no logs are displayed](#kibana-app-works-but-no-logs-are-displayed) +- [How to change default CVAT hostname or port](#how-to-change-default-cvat-hostname-or-port) +- [How to configure connected share folder on Windows](#how-to-configure-connected-share-folder-on-windows) +- [How to make unassigned tasks not visible to all users](#how-to-make-unassigned-tasks-not-visible-to-all-users) +- [Where are uploaded images/videos stored](#where-are-uploaded-imagesvideos-stored) +- [Where are annotations stored](#where-are-annotations-stored) +- [How to mark job/task as completed](#how-to-mark-jobtask-as-completed) +- [How to install CVAT on Windows 10 Home](#how-to-install-cvat-on-windows-10-home) +- [I do not have the Analytics tab on the header section. How can I add analytics](#i-do-not-have-the-analytics-tab-on-the-header-section-how-can-i-add-analytics) +- [How to upload annotations to an entire task from UI when there are multiple jobs in the task](#how-to-upload-annotations-to-an-entire-task-from-ui-when-there-are-multiple-jobs-in-the-task) + +## How to update CVAT +Before upgrading, please follow the official docker +[manual](https://docs.docker.com/storage/volumes/#backup-restore-or-migrate-data-volumes) and backup all CVAT volumes. + +To update CVAT, you should clone or download the new version of CVAT and rebuild the CVAT docker images as usual. +```sh +docker-compose build +``` +and run containers: +```sh +docker-compose up -d +``` + +Sometimes the update process takes a lot of time due to changes in the database schema and data. +You can check the current status with `docker logs cvat`. +Please do not terminate the migration and wait till the process is complete. + +## Kibana app works, but no logs are displayed +Make sure there aren't error messages from Elasticsearch: +```sh +docker logs cvat_elasticsearch +``` +If you see errors like this: +```sh +lood stage disk watermark [95%] exceeded on [uMg9WI30QIOJxxJNDiIPgQ][uMg9WI3][/usr/share/elasticsearch/data/nodes/0] free: 116.5gb[4%], all indices on this node will be marked read-only +``` +You should free up disk space or change the threshold, to do so check: [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/disk-allocator.html). + +## How to change default CVAT hostname or port +The best way to do that is to create docker-compose.override.yml and override the host and port settings here. + +version: "2.3" +```yaml +services: + cvat_proxy: + environment: + CVAT_HOST: example.com + ports: + - "80:80" +``` + +Please don't forget to include this file in docker-compose commands +using the `-f` option (in some cases it can be omitted). + +## How to configure connected share folder on Windows +Follow the Docker manual and configure the directory that you want to use as a shared directory: +- [Docker toolbox manual](https://docs.docker.com/toolbox/toolbox_install_windows/#optional-add-shared-directories) +- [Docker for windows (see FILE SHARING section)](https://docs.docker.com/docker-for-windows/#resources) + +After that, it should be possible to use this directory as a CVAT share: +```yaml +version: "2.3" + +services: + cvat: + volumes: + - cvat_share:/home/django/share:ro + +volumes: + cvat_share: + driver_opts: + type: none + device: /d/my_cvat_share + o: bind +``` + +## How to make unassigned tasks not visible to all users +Set [reduce_task_visibility](../../settings/base.py#L424) variable to `True`. + +## Where are uploaded images/videos stored +The uploaded data is stored in the `cvat_data` docker volume: +```yml +volumes: + - cvat_data:/home/django/data +``` + +## Where are annotations stored +Annotations are stored in the PostgreSQL database. The database files are stored in the `cvat_db` docker volume: +```yml +volumes: + - cvat_db:/var/lib/postgresql/data +``` + +## How to mark job/task as completed +The status is set by the user in the [Info window](user_guide.md#info) of the job annotation view. +There are three types of status: annotation, validation or completed. +The status of the job changes the progress bar of the task. + +## How to install CVAT on Windows 10 Home +Follow this [guide](installation.md#windows-10). + +## I do not have the Analytics tab on the header section. How can I add analytics +You should build CVAT images with ['Analytics' component](../../../components/analytics). + +## How to upload annotations to an entire task from UI when there are multiple jobs in the task +You can upload annotation for a multi-job task from the Dasboard view or the Task view. +Uploading of annotation from the Annotation view only affects the current job. diff --git a/cvat/apps/documentation/installation.md b/cvat/apps/documentation/installation.md index ec59168e..ce2e9008 100644 --- a/cvat/apps/documentation/installation.md +++ b/cvat/apps/documentation/installation.md @@ -3,11 +3,20 @@ - [Windows 10](#windows-10) - [Mac OS Mojave](#mac-os-mojave) - [Advanced topics](#advanced-topics) + - [Deploying CVAT behind a proxy](#deploying-cvat-behind-a-proxy) - [Additional components](#additional-components) + - [Semi-automatic and automatic annotation](#semi-automatic-and-automatic-annotation) - [Stop all containers](#stop-all-containers) - [Advanced settings](#advanced-settings) - [Share path](#share-path) - - [Serving over HTTPS](#serving-over-https) + - [Email verification](#email-verification) + - [Serving over HTTPS](#serving-over-https) + - [Prerequisites](#prerequisites) + - [Roadmap](#roadmap) + - [Step-by-step instructions](#step-by-step-instructions) + - [1. Make the proxy listen on standard port 80 and prepare nginx for the ACME challenge via webroot method](#1-make-the-proxy-listen-on-standard-port-80-and-prepare-nginx-for-the-acme-challenge-via-webroot-method) + - [2. Setting up HTTPS with `acme.sh` helper](#2-setting-up-https-with-acmesh-helper) + - [Create certificate files using an ACME challenge on docker host](#create-certificate-files-using-an-acme-challenge-on-docker-host) # Quick installation guide @@ -113,10 +122,14 @@ server. Proxy is an advanced topic and it is not covered by the guide. [CVAT user's guide](/cvat/apps/documentation/user_guide.md) for more details. ## Windows 10 -- Download [Docker for Windows](https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe). - Double-click `Docker for Windows Installer` to run the installer. More - instructions can be found [here](https://docs.docker.com/docker-for-windows/install/). Note: - Docker Desktop requires Windows 10 Pro or Enterprise version 14393 to run. +- Install WSL2 (Windows subsystem for Linux) refer to [this official guide](https://docs.microsoft.com/windows/wsl/install-win10). + WSL2 requires Windows 10, version 2004 or higher. Note: you may not install any Linux distribution unless necessary. + +- Download and install [Docker Desktop for Windows](https://download.docker.com/win/stable/Docker%20Desktop%20Installer.exe). + Double-click `Docker for Windows Installer` to run the installer. + More instructions can be found [here](https://docs.docker.com/docker-for-windows/install/). + Official guide for docker WSL2 backend can be found + [here](https://docs.docker.com/docker-for-windows/wsl/). Note: check that using exaclty WSL2 backend for docker. - Download and install [Git for Windows](https://github.com/git-for-windows/git/releases/download/v2.21.0.windows.1/Git-2.21.0-64-bit.exe). @@ -234,24 +247,66 @@ server. Proxy is an advanced topic and it is not covered by the guide. ## Advanced topics +### Deploying CVAT behind a proxy +If you deploy CVAT behind a proxy and do not plan to use any of [serverless functions](#semi-automatic-and-automatic-annotation) +for automatic annotation, the exported environment variables +`http_proxy`, `https_proxy` and `no_proxy` should be enough to build images. +Otherwise please create or edit the file `~/.docker/config.json` in the home directory of the user +which starts containers and add JSON such as the following: +```json +{ + "proxies": + { + "default": + { + "httpProxy": "http://proxy_server:port", + "httpsProxy": "http://proxy_server:port", + "noProxy": "*.test.example.com,.example2.com" + } + } +} +``` +These environment variables are set automatically within any container. +Please see the [Docker documentation](https://docs.docker.com/network/proxy/) for more details. + ### Additional components -- [Auto annotation using DL models in OpenVINO toolkit format](/cvat/apps/auto_annotation/README.md) - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) -- [TF Object Detection API: auto annotation](/components/tf_annotation/README.md) -- [Support for NVIDIA GPUs](/components/cuda/README.md) -- [Semi-automatic segmentation with Deep Extreme Cut](/cvat/apps/dextr_segmentation/README.md) -- [Auto segmentation: Keras+Tensorflow Mask R-CNN Segmentation](/components/auto_segmentation/README.md) ```bash -# Build and run containers with CUDA and OpenVINO support -# IMPORTANT: need to download OpenVINO package before running the command -docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml -f components/openvino/docker-compose.openvino.yml up -d --build - # Build and run containers with Analytics component support: docker-compose -f docker-compose.yml -f components/analytics/docker-compose.analytics.yml up -d --build ``` +### Semi-automatic and automatic annotation + +- You have to install `nuctl` command line tool to build and deploy serverless +functions. Download [the latest release](https://github.com/nuclio/nuclio/releases). +- Create `cvat` project inside nuclio dashboard where you will deploy new +serverless functions and deploy a couple of DL models. Commands below should +be run only after CVAT has been installed using docker-compose because it +runs nuclio dashboard which manages all serverless functions. + +```bash +nuctl create project cvat +``` + +```bash +nuctl deploy --project-name cvat \ + --path serverless/openvino/dextr/nuclio \ + --volume `pwd`/serverless/openvino/common:/opt/nuclio/common \ + --platform local +``` + +```bash +nuctl deploy --project-name cvat \ + --path serverless/openvino/omz/public/yolo-v3-tf/nuclio \ + --volume `pwd`/serverless/openvino/common:/opt/nuclio/common \ + --platform local +``` + +Note: see [deploy.sh](/serverless/deploy.sh) script for more examples. + ### Stop all containers The command below stops and removes containers, networks, volumes, and images @@ -308,50 +363,78 @@ You can change the share device path to your actual share. For user convenience we have defined the environment variable $CVAT_SHARE_URL. This variable contains a text (url for example) which is shown in the client-share browser. +### Email verification + +You can enable email verification for newly registered users. +Specify these options in the [settings file](../../settings/base.py) to configure Django allauth +to enable email verification (ACCOUNT_EMAIL_VERIFICATION = 'mandatory'). +Access is denied until the user's email address is verified. +```python +ACCOUNT_AUTHENTICATION_METHOD = 'username' +ACCOUNT_CONFIRM_EMAIL_ON_GET = True +ACCOUNT_EMAIL_REQUIRED = True +ACCOUNT_EMAIL_VERIFICATION = 'mandatory' + +# Email backend settings for Django +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +``` +Also you need to configure the Django email backend to send emails. +This depends on the email server you are using and is not covered in this tutorial, please see +[Django SMTP backend configuration](https://docs.djangoproject.com/en/3.1/topics/email/#django.core.mail.backends.smtp.EmailBackend) +for details. + ### Serving over HTTPS We will add [letsencrypt.org](https://letsencrypt.org/) issued certificate to secure -our server connection. +our server connection. #### Prerequisites -We assume that +We assume that + +- you have sudo access on your server machine, +- you have an IP address to use for remote access, and +- that the local CVAT installation works on your server. -- you have sudo access on your server machine, -- you have an IP address to use for remote access, and -- that the local CVAT installation works on your server. - If this is not the case, please complete the steps in the installation manual first. #### Roadmap We will go through the following sequence of steps to get CVAT over HTTPS: -- Move Docker Compose CVAT access port to 80/tcp. -- Configure Nginx to pass one of the [ACME challenges](https://letsencrypt.org/docs/challenge-types/). +- Setup containers on default 80/tcp port. Checkin and then down the containers. +- Configure Nginx to pass one of the [ACME challenges](https://letsencrypt.org/docs/challenge-types/) - webroot. - Create the certificate files using [acme.sh](https://github.com/acmesh-official/acme.sh). - Reconfigure Nginx to serve over HTTPS and map CVAT to Docker Compose port 443. #### Step-by-step instructions -##### 1. Move the CVAT access port +##### 1. Make the proxy listen on standard port 80 and prepare nginx for the ACME challenge via webroot method + +> The configuration assumes that on the docker host there will be only one instance of the CVAT site listens for incoming connections on 80 and 443 port. Also redirecting everything that does not concern renewal of certificates to the site via secure HTTPS protocol. -Let's assume the server will be at `my-cvat-server.org`. +Let's assume the server will be at `my-cvat-server.org`. + +Point you shell in cvat repository directory, usually `cd $HOME/cvat`: + +Add the following into your `docker-compose.override.yml`, replacing `my-cvat-server.org` with your own IP address. This file lives in the same directory as `docker-compose.yml`. + +Create enough directories for letsencrypt webroot operation and acme folder passthrougth. + + and restart containers with a new configuration updated in `docker-compose.override.yml` ```bash -# on the server -docker-compose down +# on the docker host -# add docker-compose.override.yml as per instructions below +# this will create ~/.acme.sh directory +curl https://get.acme.sh | sh -docker-compose up -d +# create a subdirs for acme-challenge webroot manually +mkdir -p $HOME/cvat/letsencrypt-webroot/.well-known/acme-challenge ``` -Add the following into your `docker-compose.override.yml`, replacing `my-cvat-server.org` with your own IP address. -This file lives in the same directory as `docker-compose.yml`. - ```yaml -# docker-compose.override.yml +# docker-compose.override.yml version: "2.3" services: @@ -359,177 +442,106 @@ services: environment: CVAT_HOST: my-cvat-server.org ports: - - "80:80" - + - "80:80" + - "443:443" + volumes: + - ./letsencrypt-webroot:/var/tmp/letsencrypt-webroot + - /root/.acme.sh:/root/.acme.sh + cvat: environment: ALLOWED_HOSTS: '*' ``` -You should now see an unsecured version of CVAT at `http://my-cvat-server.org`. +This will enable serving `http://my-cvat-server.org/.well-known/acme-challenge/` +route from `/var/tmp/letsencrypt-webroot` directory on the container's filesystem which is bind mounted from docker host `$HOME/cvat/letsencrypt-webroot`. That volume needed for issue and renewing certificates only. + +Update a CVAT site proxy template `$HOME/cvat/cvat_proxy/conf.d/cvat.conf.template` on docker(system) host. Site config updates from this template each time `cvat_proxy` container start. -##### 2. Configure Nginx for the ACME challenge +Add a location to server with `server_name ${CVAT_HOST};` ahead others: -Temporarily, enable serving `http://my-cvat-server.org/.well-known/acme-challenge/` -route from `/letsencrypt` directory on the server's filesystem. +``` + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/tmp/letsencrypt-webroot; + } +``` You can use the [Nginx quickstart guide](http://nginx.org/en/docs/beginners_guide.html) for reference. + ```bash -# cvat_proxy/conf.d/cvat.conf.template +# on the docker host +docker-compose down +docker-compose up -d +``` -server { - listen 80; - server_name _ default; - return 404; -} +Your server should still be visible (and unsecured) at `http://my-cvat-server.org` +but you won't see any behavior changes. -server { - listen 80; - server_name ${CVAT_HOST}; +At this point your deployment is up and running, ready for run acme-challenge. - # add this temporarily, to pass an acme challenge - location ^~ /.well-known/acme-challenge/ { - allow all; - root /letsencrypt; - } +##### 2. Setting up HTTPS with `acme.sh` helper - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { - proxy_pass http://cvat:8080; - proxy_pass_header X-CSRFToken; - proxy_set_header Host $http_host; - proxy_pass_header Set-Cookie; - } +There are multiple approaches. First one is to use helper on docker host. - location / { - # workaround for match location by arguments - error_page 418 = @annotation_ui; +In a our approach +* it is easier to setup automatic certificate updates and (than it can be done in the container). +* leave certificates in safe place on docker host (protect from `docker-compose down` cleanup) +* no unnecessary certificate files copying between container and host. - if ( $query_string ~ "^id=\d+.*" ) { return 418; } +###### Create certificate files using an ACME challenge on docker host - proxy_pass http://cvat_ui; - proxy_pass_header X-CSRFToken; - proxy_set_header Host $http_host; - proxy_pass_header Set-Cookie; - } +**Prepare certificates.** - # old annotation ui, will be removed in the future. - location @annotation_ui { - proxy_pass http://cvat:8080; - proxy_pass_header X-CSRFToken; - proxy_set_header Host $http_host; - proxy_pass_header Set-Cookie; - } -} -``` +Point you shell in cvat repository directory, usually `cd $HOME/cvat` on docker host. -Now create the `/letsencrypt` directory and mount it into `cvat_proxy` container. -Edit your `docker-compose.override.yml` to look like the following: +> Certificate issue and updates should be on docker host in this approach. -```yaml -# docker-compose.override.yml -version: "2.3" +Let’s Encrypt provides rate limits to ensure fair usage by as many people as possible. They recommend utilize their staging environment instead of the production API during testing. So first try to get a test certificate. -services: - cvat_proxy: - environment: - CVAT_HOST: my-cvat-server.org - ports: - - "80:80" - volumes: - - ./letsencrypt:/letsencrypt - - cvat: - environment: - ALLOWED_HOSTS: '*' +``` +~/.acme.sh/acme.sh --issue --staging -d my-cvat-server.org -w $HOME/cvat/letsencrypt-webroot --debug ``` -Finally, create the directory and restart CVAT. +> Debug note: nginx server logs for cvat_proxy are not saved in container. You shall see it at docker host by with: `docker logs cvat_proxy`. -```bash -# in the same directory where docker-compose.override.yml lives -mkdir -p letsencrypt/.well-known/acme-challenge +If certificates is issued a successful we should test a renew: -docker-compose down -docker-compose up -d ``` +~/.acme.sh/acme.sh --renew --force --staging -d my-cvat-server.org -w $HOME/cvat/letsencrypt-webroot --debug +``` +If success: -Your server should still be visible (and unsecured) at `http://my-cvat-server.org` -but you won't see any behavior changes. +* remove test certificate +* issue a production certificate +* create a cron job for user (`crontab -e`). -##### 3. Create certificate files using an ACME challenge +``` +~/.acme.sh/acme.sh --remove -d my-cvat-server.org --debug +rm -r /root/.acme.sh/my-cvat-server.org -At this point your deployment is running. +~/.acme.sh/acme.sh --issue -d my-cvat-server.org -w $HOME/cvat/letsencrypt-webroot --debug -```bash -admin@tempVM:~/cvat$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -0a35cd127968 nginx:stable-alpine "/bin/sh -c 'envsubs…" About a minute ago Up About a minute 0.0.0.0:80->80/tcp, 0.0.0.0:8080->80/tcp cvat_proxy -b85497c44836 cvat_cvat_ui "nginx -g 'daemon of…" About a minute ago Up About a minute 80/tcp cvat_ui -d25a00475849 cvat "/usr/bin/supervisord" About a minute ago Up About a minute 8080/tcp, 8443/tcp cvat -6353a43f55c3 redis:4.0-alpine "docker-entrypoint.s…" About a minute ago Up About a minute 6379/tcp cvat_redis -52009636caa8 postgres:10-alpine "docker-entrypoint.s…" About a minute ago Up About a minute 5432/tcp cvat_db +~/.acme.sh/acme.sh --install-cronjob ``` -We will attach `cvat_proxy` container to run `acme.sh` scripts. +Down the cvat_proxy container for setup https with issued certificates. ```bash -admin@tempVM:~/cvat$ docker exec -ti cvat_proxy /bin/sh - -# install some missing software inside cvat_proxy -/ # apk add openssl curl -/ # curl https://get.acme.sh | sh -/ # ~/.acme.sh/acme.sh -h -[... many lines ...] - -/ # ~/.acme.sh/acme.sh --issue -d my-cvat-server.org -w /letsencrypt -[Fri Apr 3 20:49:05 UTC 2020] Create account key ok. -[Fri Apr 3 20:49:05 UTC 2020] Registering account -[Fri Apr 3 20:49:06 UTC 2020] Registered -[Fri Apr 3 20:49:06 UTC 2020] ACCOUNT_THUMBPRINT='tril8-LdJgM8xg6mnN1pMa7vIMdFizVCE0NImNmyZY4' -[Fri Apr 3 20:49:06 UTC 2020] Creating domain key -[ ... many more lines ...] -[Fri Apr 3 20:49:10 UTC 2020] Your cert is in /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.cer -[Fri Apr 3 20:49:10 UTC 2020] Your cert key is in /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.key -[Fri Apr 3 20:49:10 UTC 2020] The intermediate CA cert is in /root/.acme.sh/my-cvat-server.org/ca.cer -[Fri Apr 3 20:49:10 UTC 2020] And the full chain certs is there: /root/.acme.sh/my-cvat-server.org/fullchain.cer - -/ # cp ~/.acme.sh/my-cvat-server.org/my-cvat-server.org.cer /letsencrypt/certificate.cer -/ # cp ~/.acme.sh/my-cvat-server.org/my-cvat-server.org.key /letsencrypt/certificate.key -/ # cp ~/.acme.sh/my-cvat-server.org/ca.cer /letsencrypt/ca.cer -/ # cp ~/.acme.sh/my-cvat-server.org/fullchain.cer /letsencrypt/fullchain.cer -/ # exit -admin@tempVM:~/cvat$ ls letsencrypt/ -ca.cer certificate.cer certificate.key fullchain.cer -admin@tempVM:~/cvat$ mkdir cert -admin@tempVM:~/cvat$ mv letsencrypt/* ./cert +docker stop cvat_proxy ``` -##### 4. Reconfigure Nginx for HTTPS access +**Reconfigure nginx for use certificates.** -Update Docker Compose configuration to mount the certificate directory. +Bring the configuration file `$HOME/cvat/cvat_proxy/conf.d/cvat.conf.template` to the following form: -```yml -# docker-compose.override.yml -version: "2.3" +* add location with redirect `return 301` to 80/tcp server. +* change listen to `listen 443 ssl;` in main configururation server +* add ssl certificates options and secure them. -services: - cvat_proxy: - environment: - CVAT_HOST: my-cvat-server.org - ports: - - "443:443" - volumes: - - ./letsencrypt:/letsencrypt - - ./cert:/cert:ro # this is new - - cvat: - environment: - ALLOWED_HOSTS: '*' -``` - -Also, reconfigure Nginx to use `443/tcp` and point it to the new keys. +Example of configuration file: -```bash +``` server { listen 80; server_name _ default; @@ -537,68 +549,59 @@ server { } server { - listen 443 ssl; + listen 80; + server_name ${CVAT_HOST}; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/tmp/letsencrypt; + } + + location / { + return 301 https://$server_name$request_uri; + } +} + +server { + listen 443 ssl; + ssl_certificate /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.cer; + ssl_certificate_key /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.key; + ssl_trusted_certificate /root/.acme.sh/my-cvat-server.org/fullchain.cer; + + # security options + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_stapling on; + ssl_session_timeout 24h; + ssl_session_cache shared:SSL:2m; + ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!3DES'; + + # security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + server_name ${CVAT_HOST}; proxy_pass_header X-CSRFToken; proxy_set_header Host $http_host; proxy_pass_header Set-Cookie; - ssl_certificate /cert/certificate.cer; - ssl_certificate_key /cert/certificate.key; - - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { + location ~* /api/.*|git/.*|analytics/.*|static/.*|admin(?:/(.*))?.*|documentation/.*|django-rq(?:/(.*))? { proxy_pass http://cvat:8080; } - # workaround for match location by arguments - location = / { - error_page 418 = @annotation_ui; - - if ( $query_string ~ "^id=\d+.*" ) { return 418; } - proxy_pass http://cvat_ui; - } - location / { proxy_pass http://cvat_ui; } - - # old annotation ui, will be removed in the future. - location @annotation_ui { - proxy_pass http://cvat:8080; - } } ``` -Finally, restart your service. +Start cvat_proxy container with https enabled. ```bash -admin@tempVM:~/cvat$ docker-compose down -Stopping cvat_proxy ... done -Stopping cvat_ui ... done -Stopping cvat ... done -Stopping cvat_db ... done -Stopping cvat_redis ... done -Removing cvat_proxy ... done -Removing cvat_ui ... done -Removing cvat ... done -Removing cvat_db ... done -Removing cvat_redis ... done -Removing network cvat_default -admin@tempVM:~/cvat$ docker-compose up -d -Creating network "cvat_default" with the default driver -Creating cvat_db ... done -Creating cvat_redis ... done -Creating cvat ... done -Creating cvat_ui ... done -Creating cvat_proxy ... done -admin@tempVM:~/cvat$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -71464aeac87c nginx:stable-alpine "/bin/sh -c 'envsubs…" About a minute ago Up About a minute 0.0.0.0:443->443/tcp, 0.0.0.0:8080->80/tcp cvat_proxy -8428cfbb766e cvat_cvat_ui "nginx -g 'daemon of…" About a minute ago Up About a minute 80/tcp cvat_ui -b5a2f78689da cvat "/usr/bin/supervisord" About a minute ago Up About a minute 8080/tcp, 8443/tcp cvat -ef4a1f47440f redis:4.0-alpine "docker-entrypoint.s…" About a minute ago Up About a minute 6379/tcp cvat_redis -7803bf828d9f postgres:10-alpine "docker-entrypoint.s…" About a minute ago Up About a minute 5432/tcp cvat_db +docker start cvat_proxy ``` - -Now you can go to `https://my-cvat-server.org/` and verify that you are using an encrypted connection. diff --git a/cvat/apps/documentation/migrations/__init__.py b/cvat/apps/documentation/migrations/__init__.py deleted file mode 100644 index d8e62e54..00000000 --- a/cvat/apps/documentation/migrations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - diff --git a/cvat/apps/documentation/models.py b/cvat/apps/documentation/models.py deleted file mode 100644 index cdf3b082..00000000 --- a/cvat/apps/documentation/models.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.db import models - -# Create your models here. - diff --git a/cvat/apps/documentation/static/documentation/images/image067.jpg b/cvat/apps/documentation/static/documentation/images/image067.jpg index ce8cba2b..25161e07 100644 Binary files a/cvat/apps/documentation/static/documentation/images/image067.jpg and b/cvat/apps/documentation/static/documentation/images/image067.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image155.jpg b/cvat/apps/documentation/static/documentation/images/image155.jpg index 5081f66d..5efa220b 100644 Binary files a/cvat/apps/documentation/static/documentation/images/image155.jpg and b/cvat/apps/documentation/static/documentation/images/image155.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image181.jpg b/cvat/apps/documentation/static/documentation/images/image181.jpg index 1714be9e..81181476 100644 Binary files a/cvat/apps/documentation/static/documentation/images/image181.jpg and b/cvat/apps/documentation/static/documentation/images/image181.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image183.jpg b/cvat/apps/documentation/static/documentation/images/image183.jpg new file mode 100644 index 00000000..0f0cffd2 Binary files /dev/null and b/cvat/apps/documentation/static/documentation/images/image183.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image184.jpg b/cvat/apps/documentation/static/documentation/images/image184.jpg new file mode 100644 index 00000000..02f09907 Binary files /dev/null and b/cvat/apps/documentation/static/documentation/images/image184.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image185_detrac.jpg b/cvat/apps/documentation/static/documentation/images/image185_detrac.jpg new file mode 100644 index 00000000..1bb1ea5c Binary files /dev/null and b/cvat/apps/documentation/static/documentation/images/image185_detrac.jpg differ diff --git a/cvat/apps/documentation/static/documentation/images/image186_detrac.jpg b/cvat/apps/documentation/static/documentation/images/image186_detrac.jpg new file mode 100644 index 00000000..c14d0322 Binary files /dev/null and b/cvat/apps/documentation/static/documentation/images/image186_detrac.jpg differ diff --git a/cvat/apps/documentation/tests.py b/cvat/apps/documentation/tests.py deleted file mode 100644 index 53bc3b7a..00000000 --- a/cvat/apps/documentation/tests.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.test import TestCase - -# Create your tests here. - diff --git a/cvat/apps/documentation/user_guide.md b/cvat/apps/documentation/user_guide.md index 0a01c1d0..f135b671 100644 --- a/cvat/apps/documentation/user_guide.md +++ b/cvat/apps/documentation/user_guide.md @@ -32,6 +32,7 @@ - [Linear interpolation with one point](#linear-interpolation-with-one-point) - [Annotation with cuboids](#annotation-with-cuboids) - [Annotation with tags](#annotation-with-tags) + - [Track mode with polygons](#track-mode-with-polygons) - [Automatic annotation](#automatic-annotation) - [Shape grouping](#shape-grouping) - [Filter](#filter) @@ -152,7 +153,7 @@ Go to the [Django administration panel](http://localhost:8080/admin). There you **Image Quality**. Use this option to specify quality of uploaded images. The option helps to load high resolution datasets faster. - Use the value from ``1`` (completely compressed images) to ``95`` (almost not compressed images). + Use the value from ``5`` (almost completely compressed images) to ``100`` (not compressed images). **Overlap Size**. Use this option to make overlapped segments. The option makes tracks continuous from one segment into another. @@ -638,6 +639,7 @@ To open the settings open the user menu in the header and select the settings it In tab ``Player`` you can: - Control step of ``C`` and ``V`` shortcuts. - Control speed of ``Space``/``Play`` button. +- Select canvas background color. You can choose a background color or enter manually (in RGB or HEX format). - Show ``Grid``, change grid size, choose color and transparency: ![](static/documentation/images/image068_mapillary_vistas.jpg) @@ -798,7 +800,7 @@ Switching between user interface modes. |Icon |Description |Links to section | |-- |-- |-- | |![](static/documentation/images/image167.jpg)|``Rectangle``|[Shape mode](#shape-mode-basics); [Track mode](#track-mode-basics);
[Drawing by 4 points](#annotation-with-rectangle-by-4-points)| -|![](static/documentation/images/image168.jpg)|``Polygon`` |[Annotation with polygons](#annotation-with-polygons) | +|![](static/documentation/images/image168.jpg)|``Polygon`` |[Annotation with polygons](#annotation-with-polygons); [Track mode with polygons](#track-mode-with-polygons) | |![](static/documentation/images/image169.jpg)|``Polyline`` |[Annotation with polylines](#annotation-with-polylines)| |![](static/documentation/images/image170.jpg)|``Points`` |[Annotation with points](#annotation-with-points) | |![](static/documentation/images/image176.jpg)|``Cuboid`` |[Annotation with cuboids](#annotation-with-cuboids) | @@ -1067,7 +1069,7 @@ Before starting, you need to select ``Polygon`` on the controls sidebar and choo delete the previous point by right-clicking on it. - Press ``N`` again for completing the shape. - After creating the polygon, you can move the points or delete them by right-clicking and selecting ``Delete point`` - or double-clicking with pressed ``Ctrl`` key in the context menu. + or clicking with pressed ``Ctrl`` key in the context menu. ### Drawing using automatic borders @@ -1126,7 +1128,7 @@ Used to create a polygon semi-automatically. ### Edit polygon -To edit a polygon you have to double-click with pressed ``Shift``, it will open the polygon editor. +To edit a polygon you have to click with pressed ``Shift``, it will open the polygon editor. - There you can create new points or delete part of a polygon closing the line on another point. - After closing the polygon, you can select the part of the polygon that you want to leave. - You can press ``Esc`` to cancel editing. @@ -1147,8 +1149,8 @@ you either create points by clicking or by dragging a mouse on the screen while When ``Shift`` isn't pressed, you can zoom in/out (when scrolling the mouse wheel) and move (when clicking the mouse wheel and moving the mouse), you can delete previous points by right-clicking on it. Press ``N`` again to complete the shape. -You can delete a point by double-clicking on it with pressed ``Ctrl`` or right-clicking on a point -and selecting ``Delete point``. Double-click with pressed ``Shift`` will open a polyline editor. +You can delete a point by clicking on it with pressed ``Ctrl`` or right-clicking on a point +and selecting ``Delete point``. Click with pressed ``Shift`` will open a polyline editor. There you can create new points(by clicking or dragging) or delete part of a polygon closing the red line on another point. Press ``Esc`` to cancel editing. @@ -1167,8 +1169,8 @@ in the ``Number of points`` field, then drawing will be stopped automatically. Click ``Shape`` to entering the drawing mode. Now you can start annotation of the necessary area. Points are automatically grouped — all points will be considered linked between each start and finish. -Press ``N`` again to finish marking the area. You can delete a point by double-clicking with pressed ``Ctrl`` -or right-clicking on a point and selecting ``Delete point``. Double-clicking with pressed ``Shift`` will open the points +Press ``N`` again to finish marking the area. You can delete a point by clicking with pressed ``Ctrl`` +or right-clicking on a point and selecting ``Delete point``. Clicking with pressed ``Shift`` will open the points shape editor. There you can add new points into an existing shape. You can zoom in/out (when scrolling the mouse wheel) and move (when clicking the mouse wheel and moving the mouse) while drawing. You can drag an object after it has been drawn and change the position of individual points after finishing an object. @@ -1270,20 +1272,52 @@ Simply drag the faces to move them independently from the rest of the cuboid. ![](static/documentation/images/gif020_mapillary_vistas.gif) -You can also use cuboids in track mode, similar to rectangles in track mode ([basics](#track-mode-basics) and [advanced](#track-mode-advanced)) +You can also use cuboids in track mode, similar to rectangles in track mode ([basics](#track-mode-basics) and [advanced](#track-mode-advanced)) or [Track mode with polygons](#track-mode-with-polygons) ## Annotation with Tags -Used to annotate frames, does not have a shape in the workspace. -Before you start, you have to make sure that Tag is selected. +It is used to annotate frames, tags are not displayed in the workspace. +Before you start, open the drop-down list in the top panel and select ``Tag annotation``. + +![](static/documentation/images/image183.jpg) + +The objects sidebar will be replaced with a special panel for working with tags. +Here you can select a label for a tag and add it by clicking on the ``Add tag`` button. +You can also customize hotkeys for each label. ![](static/documentation/images/image181.jpg) -Click tag to create. You can work with Tag only on the sidebar. -You can use the lock function and change label and attribute. -Other functions such as propagate, make a copy and remove are available in the action menu. +If you need to use only one label for one frame, then enable the ``Automatically go to the next frame`` +checkbox, then after you add the tag the frame will automatically switch to the next. + +## Track mode with polygons + +Polygons in the track mode allow you to mark moving objects more accurately other than using a rectangle + ([Tracking mode (basic)](#track-mode-basics); [Tracking mode (advanced)](#track-mode-advanced)). +1. To create a polygon in the track mode, click the ``Track`` button. + + ![](static/documentation/images/image184.jpg) + +1. Create a polygon the same way as in the case of [Annotation with polygons](#annotation-with-polygons). + Press ``N`` to complete the polygon. + +1. Pay attention to the fact that the created polygon has a starting point and a direction, + these elements are important for annotation of the following frames. + +1. After going a few frames forward press ``Shift+N``, the old polygon will disappear and you can create a new polygon. + The new starting point should match the starting point of the previously created polygon + (in this example, the top of the left mirror). The direction must also match (in this example, clockwise). + After creating the polygon, press ``N`` and the intermediate frames will be interpolated automatically. + + ![](static/documentation/images/image185_detrac.jpg) + +1. If you need to change the starting point, right-click on the desired point and select ``Set starting point``. + To change the direction, right-click on the desired point and select switch orientation. + + ![](static/documentation/images/image186_detrac.jpg) -![](static/documentation/images/image135.jpg) +There is no need to redraw the polygon every time using ``Shift+N``, + instead you can simply move the points or edit a part of the polygon by pressing ``Shift+Click``. ## Automatic annotation @@ -1461,6 +1495,7 @@ Many UI elements have shortcut hints. Put your pointer to a required element to | | _Modes_ | | ``N`` | Repeat the latest procedure of drawing with the same parameters | | ``M`` | Activate or deactivate mode to merging shapes | +| ``Alt+M`` | Activate or deactivate mode to spliting shapes | | ``G`` | Activate or deactivate mode to grouping shapes | | ``Shift+G`` | Reset group for selected shapes (in group mode) | | ``Esc`` | Cancel any active canvas mode | @@ -1479,8 +1514,8 @@ Many UI elements have shortcut hints. Put your pointer to a required element to | | _Operations with objects_ | | ``Ctrl`` | Switch automatic bordering for polygons and polylines during drawing/editing | | Hold ``Ctrl`` | When the shape is active and fix it | -| ``Ctrl+Double-Click`` on point | Deleting a point (used when hovering over a point of polygon, polyline, points) | -| ``Shift+Double-Click`` on point| Editing a shape (used when hovering over a point of polygon, polyline or points)| +| ``Ctrl+Click`` on point | Deleting a point (used when hovering over a point of polygon, polyline, points) | +| ``Shift+Click`` on point | Editing a shape (used when hovering over a point of polygon, polyline or points)| | ``Right-Click`` on shape | Display of an object element from objects sidebar | | ``T+L`` | Change locked state for all objects in the sidebar | | ``L`` | Change locked state for an active object | diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 3070dca7..dea14183 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -10,7 +10,6 @@ import io from abc import ABC, abstractmethod import av -import av.datasets import numpy as np from pyunpack import Archive from PIL import Image, ImageFile @@ -234,7 +233,7 @@ class VideoReader(IMediaReader): return pos / stream.duration if stream.duration else None def _get_av_container(self): - return av.open(av.datasets.curated(self._source_path[0])) + return av.open(self._source_path[0]) def get_preview(self): container = self._get_av_container() diff --git a/cvat/apps/engine/migrations/0026_auto_20200719_1511.py b/cvat/apps/engine/migrations/0026_auto_20200719_1511.py new file mode 100644 index 00000000..62c996f6 --- /dev/null +++ b/cvat/apps/engine/migrations/0026_auto_20200719_1511.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.13 on 2020-07-19 15:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('engine', '0025_auto_20200324_1222'), + ] + + operations = [ + migrations.AlterField( + model_name='labeledshape', + name='type', + field=models.CharField(choices=[('rectangle', 'RECTANGLE'), ('polygon', 'POLYGON'), ('polyline', 'POLYLINE'), ('points', 'POINTS'), ('cuboid', 'CUBOID')], max_length=16), + ), + migrations.AlterField( + model_name='trackedshape', + name='type', + field=models.CharField(choices=[('rectangle', 'RECTANGLE'), ('polygon', 'POLYGON'), ('polyline', 'POLYLINE'), ('points', 'POINTS'), ('cuboid', 'CUBOID')], max_length=16), + ), + ] diff --git a/cvat/apps/engine/migrations/0027_auto_20200719_1552.py b/cvat/apps/engine/migrations/0027_auto_20200719_1552.py new file mode 100644 index 00000000..4ffffd6a --- /dev/null +++ b/cvat/apps/engine/migrations/0027_auto_20200719_1552.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.10 on 2020-07-19 15:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('engine', '0026_auto_20200719_1511'), + ] + + operations = [ + migrations.AddField( + model_name='labeledimage', + name='source', + field=models.CharField(choices=[('auto', 'AUTO'), ('manual', 'MANUAL')], default='manual', max_length=16, null=True), + ), + migrations.AddField( + model_name='labeledshape', + name='source', + field=models.CharField(choices=[('auto', 'AUTO'), ('manual', 'MANUAL')], default='manual', max_length=16, null=True), + ), + migrations.AddField( + model_name='labeledtrack', + name='source', + field=models.CharField(choices=[('auto', 'AUTO'), ('manual', 'MANUAL')], default='manual', max_length=16, null=True), + ), + ] diff --git a/cvat/apps/engine/migrations/0028_labelcolor.py b/cvat/apps/engine/migrations/0028_labelcolor.py new file mode 100644 index 00000000..a725eb9a --- /dev/null +++ b/cvat/apps/engine/migrations/0028_labelcolor.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.13 on 2020-08-11 11:26 +from django.db import migrations, models +from cvat.apps.dataset_manager.formats.utils import get_label_color + +def alter_label_colors(apps, schema_editor): + Label = apps.get_model('engine', 'Label') + Task = apps.get_model('engine', 'Task') + + for task in Task.objects.all(): + labels = Label.objects.filter(task_id=task.id).order_by('id') + label_names = list() + for label in labels: + label.color = get_label_color(label.name, label_names) + label_names.append(label.name) + label.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('engine', '0027_auto_20200719_1552'), + ] + + operations = [ + migrations.AddField( + model_name='label', + name='color', + field=models.CharField(default='', max_length=8), + ), + migrations.RunPython( + code=alter_label_colors, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index c43c12c5..f3fd13b4 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -240,6 +240,7 @@ class Job(models.Model): class Label(models.Model): task = models.ForeignKey(Task, on_delete=models.CASCADE) name = SafeCharField(max_length=64) + color = models.CharField(default='', max_length=8) def __str__(self): return self.name @@ -302,12 +303,25 @@ class ShapeType(str, Enum): def __str__(self): return self.value +class SourceType(str, Enum): + AUTO = 'auto' + MANUAL = 'manual' + + @classmethod + def choices(self): + return tuple((x.value, x.name) for x in self) + + def __str__(self): + return self.value + class Annotation(models.Model): id = models.BigAutoField(primary_key=True) job = models.ForeignKey(Job, on_delete=models.CASCADE) label = models.ForeignKey(Label, on_delete=models.CASCADE) frame = models.PositiveIntegerField() group = models.PositiveIntegerField(null=True) + source = models.CharField(max_length=16, choices=SourceType.choices(), + default=str(SourceType.MANUAL), null=True) class Meta: abstract = True @@ -380,20 +394,3 @@ class TrackedShape(Shape): class TrackedShapeAttributeVal(AttributeVal): shape = models.ForeignKey(TrackedShape, on_delete=models.CASCADE) - -class Plugin(models.Model): - name = models.SlugField(max_length=32, primary_key=True) - description = SafeCharField(max_length=8192) - maintainer = models.ForeignKey(User, null=True, blank=True, - on_delete=models.SET_NULL, related_name="maintainers") - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now_add=True) - - # Extend default permission model - class Meta: - default_permissions = () - -class PluginOption(models.Model): - plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE) - name = SafeCharField(max_length=32) - value = SafeCharField(max_length=1024) diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 20791520..f211d28b 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -11,6 +11,7 @@ from django.contrib.auth.models import User, Group from cvat.apps.engine import models from cvat.apps.engine.log import slogger +from cvat.apps.dataset_manager.formats.utils import get_label_color class AttributeSerializer(serializers.ModelSerializer): @@ -37,9 +38,11 @@ class AttributeSerializer(serializers.ModelSerializer): class LabelSerializer(serializers.ModelSerializer): attributes = AttributeSerializer(many=True, source='attributespec_set', default=[]) + color = serializers.CharField(allow_blank=True, required=False) + class Meta: model = models.Label - fields = ('id', 'name', 'attributes') + fields = ('id', 'name', 'color', 'attributes') class JobCommitSerializer(serializers.ModelSerializer): class Meta: @@ -249,8 +252,12 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer): def create(self, validated_data): labels = validated_data.pop('label_set') db_task = models.Task.objects.create(**validated_data) + label_names = list() for label in labels: attributes = label.pop('attributespec_set') + if not label.get('color', None): + label['color'] = get_label_color(label['name'], label_names) + label_names.append(label['name']) db_label = models.Label.objects.create(task=db_task, **label) for attr in attributes: models.AttributeSpec.objects.create(label=db_label, **attr) @@ -285,6 +292,14 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer): else: slogger.task[instance.id].info("{} label was updated" .format(db_label.name)) + if not label.get('color', None): + label_names = [l.name for l in + models.Label.objects.filter(task_id=instance.id).exclude(id=db_label.id).order_by('id') + ] + db_label.color = get_label_color(db_label.name, label_names) + else: + db_label.color = label.get('color', db_label.color) + db_label.save() for attr in attributes: (db_attr, created) = models.AttributeSpec.objects.get_or_create( label=db_label, name=attr['name'], defaults=attr) @@ -305,6 +320,15 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer): instance.save() return instance + def validate_labels(self, value): + if not value: + raise serializers.ValidationError('Label set must not be empty') + label_names = [label['name'] for label in value] + if len(label_names) != len(set(label_names)): + raise serializers.ValidationError('All label names must be unique for the task') + return value + + class ProjectSerializer(serializers.ModelSerializer): class Meta: model = models.Project @@ -328,7 +352,7 @@ class BasicUserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ('url', 'id', 'username', 'first_name', 'last_name', 'email') + fields = ('url', 'id', 'username', 'first_name', 'last_name') ordering = ['-id'] class UserSerializer(serializers.ModelSerializer): @@ -409,6 +433,7 @@ class AnnotationSerializer(serializers.Serializer): frame = serializers.IntegerField(min_value=0) label_id = serializers.IntegerField(min_value=0) group = serializers.IntegerField(min_value=0, allow_null=True) + source = serializers.CharField(default = 'manual') class LabeledImageSerializer(AnnotationSerializer): attributes = AttributeValSerializer(many=True, @@ -450,12 +475,6 @@ class FileInfoSerializer(serializers.Serializer): name = serializers.CharField(max_length=1024) type = serializers.ChoiceField(choices=["REG", "DIR"]) -class PluginSerializer(serializers.ModelSerializer): - class Meta: - model = models.Plugin - fields = ('name', 'description', 'maintainer', 'created_at', - 'updated_at') - class LogEventSerializer(serializers.Serializer): job_id = serializers.IntegerField(required=False) task_id = serializers.IntegerField(required=False) diff --git a/cvat/apps/engine/static/engine/base.css b/cvat/apps/engine/static/engine/base.css deleted file mode 100644 index 61115dd7..00000000 --- a/cvat/apps/engine/static/engine/base.css +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -html { - background-color: #FFFFFF; - margin: 0px auto; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@font-face { - font-family: BarlowSemiCondensed-Regular; - src: url(fonts/BarlowSemiCondensed-Regular.ttf); -} - -@font-face { - font-family: BarlowSemiCondensed-SemiBold; - src: url(fonts/BarlowSemiCondensed-SemiBold.ttf); -} - -@font-face { - font-family: BarlowSemiCondensed-Bold; - src: url(fonts/BarlowSemiCondensed-Bold.ttf); -} - -.regular { - font-family: BarlowSemiCondensed-Regular; -} - -.semiBold { - font-family: BarlowSemiCondensed-SemiBold; -} - -.bold { - font-family: BarlowSemiCondensed-Bold; -} - -.h2 { - font-size: 1.2em; -} - -.h1 { - font-size: 1.4em; -} - -.h3 { - font-size: 1.1em; -} - -.overlay { - position: fixed; - display: none; - width: 100%; - height: 100%; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: white; - z-index: 100; -} - -.modal { - position: fixed; - z-index: 10; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgb(0,0,0); - background-color: rgba(0,0,0,0.4); -} - -.modal-content { - background-color: #FFFFFF; - margin: 15% auto; - padding: 20px; - border: 1px solid #888; - width: 80%; -} - -.close { - position: absolute; - right: 5px; - top: 5px; - width: 32px; - height: 32px; - opacity: 0.3; - } - - .close:hover { - opacity: 1; - } - - .close:before, .close:after { - position: absolute; - left: 15px; - content: ' '; - height: 22px; - width: 2px; - background-color: #333; - } - - .close:before { - transform: rotate(45deg); - } - - .close:after { - transform: rotate(-45deg); - } - -.tab { - overflow: hidden; - border: 1px solid black; - background-color: #B0C4DE; -} - -.tab button { - background-color: inherit; - float: left; - border: none; - outline: none; - cursor: pointer; - padding: 3px 0px; - transition: 0.3s; - -} - -.tab button:hover { - background-color: #81aee8; -} - -.tab button.active { - background-color: #ccc; -} - -.activeTabButton { - background-color: #81aee8 !important; -} - -.hidden { - display: none; -} - -.selectable { - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; -} - -.dropdown { - position: relative; -} - -.dropdown-content { - position: absolute; - background-color: #f1f1f1; - min-width: 160px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; - list-style-type: none; - padding-inline-start: 0; - margin-left: 20%; - margin-top: -1%; -} - -.dropdown-content li { - color: black; - padding: 12px 16px; - text-decoration: none; - display: block; -} - -.dropdown-content li:hover {background-color: #ddd} \ No newline at end of file diff --git a/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-Bold.ttf b/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-Bold.ttf deleted file mode 100644 index 35b5a372..00000000 Binary files a/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-Bold.ttf and /dev/null differ diff --git a/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-Regular.ttf b/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-Regular.ttf deleted file mode 100644 index ccdebedc..00000000 Binary files a/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-Regular.ttf and /dev/null differ diff --git a/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-SemiBold.ttf b/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-SemiBold.ttf deleted file mode 100644 index 660b4025..00000000 Binary files a/cvat/apps/engine/static/engine/fonts/BarlowSemiCondensed-SemiBold.ttf and /dev/null differ diff --git a/cvat/apps/engine/static/engine/fonts/README.md b/cvat/apps/engine/static/engine/fonts/README.md deleted file mode 100644 index 73a8fef3..00000000 --- a/cvat/apps/engine/static/engine/fonts/README.md +++ /dev/null @@ -1,95 +0,0 @@ -Fonts was downloaded from: https://fonts.google.com/specimen/Barlow+Semi+Condensed - -Copyright 2017 The Barlow Project Authors (https://github.com/jpt/barlow) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/cvat/apps/engine/static/engine/icons/README.md b/cvat/apps/engine/static/engine/icons/README.md deleted file mode 100644 index bad04298..00000000 --- a/cvat/apps/engine/static/engine/icons/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Icons was downloaded from: https://icomoon.io/#preview-free [GitHub](https://github.com/Keyamoon/IcoMoon-Free) - -[License](https://github.com/Keyamoon/IcoMoon-Free/blob/master/License.txt): - -You can use this package under one of these two licenses: [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/) or [GPL](http://www.gnu.org/licenses/gpl.html). diff --git a/cvat/apps/engine/static/engine/icons/copy.png b/cvat/apps/engine/static/engine/icons/copy.png deleted file mode 100644 index 5e76a30c..00000000 Binary files a/cvat/apps/engine/static/engine/icons/copy.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/hidden-shape.png b/cvat/apps/engine/static/engine/icons/hidden-shape.png deleted file mode 100644 index 60fb969e..00000000 Binary files a/cvat/apps/engine/static/engine/icons/hidden-shape.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/hidden-text.png b/cvat/apps/engine/static/engine/icons/hidden-text.png deleted file mode 100644 index 36f73c89..00000000 Binary files a/cvat/apps/engine/static/engine/icons/hidden-text.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/initKeyFrame.png b/cvat/apps/engine/static/engine/icons/initKeyFrame.png deleted file mode 100644 index 62761e7c..00000000 Binary files a/cvat/apps/engine/static/engine/icons/initKeyFrame.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/keyframe.png b/cvat/apps/engine/static/engine/icons/keyframe.png deleted file mode 100644 index 252321a8..00000000 Binary files a/cvat/apps/engine/static/engine/icons/keyframe.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/lock.png b/cvat/apps/engine/static/engine/icons/lock.png deleted file mode 100644 index c8174e56..00000000 Binary files a/cvat/apps/engine/static/engine/icons/lock.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/nextKeyFrame.png b/cvat/apps/engine/static/engine/icons/nextKeyFrame.png deleted file mode 100644 index c70aeb34..00000000 Binary files a/cvat/apps/engine/static/engine/icons/nextKeyFrame.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/non-hidden.png b/cvat/apps/engine/static/engine/icons/non-hidden.png deleted file mode 100644 index 253ce712..00000000 Binary files a/cvat/apps/engine/static/engine/icons/non-hidden.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/non-keyframe.png b/cvat/apps/engine/static/engine/icons/non-keyframe.png deleted file mode 100644 index 619dd5bd..00000000 Binary files a/cvat/apps/engine/static/engine/icons/non-keyframe.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/non-occluded.png b/cvat/apps/engine/static/engine/icons/non-occluded.png deleted file mode 100644 index 74ba4675..00000000 Binary files a/cvat/apps/engine/static/engine/icons/non-occluded.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/non-outside.png b/cvat/apps/engine/static/engine/icons/non-outside.png deleted file mode 100644 index 56771045..00000000 Binary files a/cvat/apps/engine/static/engine/icons/non-outside.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/occluded.png b/cvat/apps/engine/static/engine/icons/occluded.png deleted file mode 100644 index 9b607ca9..00000000 Binary files a/cvat/apps/engine/static/engine/icons/occluded.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/outside.png b/cvat/apps/engine/static/engine/icons/outside.png deleted file mode 100644 index 4f7cea67..00000000 Binary files a/cvat/apps/engine/static/engine/icons/outside.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/prevKeyFrame.png b/cvat/apps/engine/static/engine/icons/prevKeyFrame.png deleted file mode 100644 index 82b65200..00000000 Binary files a/cvat/apps/engine/static/engine/icons/prevKeyFrame.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/propagate.png b/cvat/apps/engine/static/engine/icons/propagate.png deleted file mode 100644 index a575441e..00000000 Binary files a/cvat/apps/engine/static/engine/icons/propagate.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/icons/unlock.png b/cvat/apps/engine/static/engine/icons/unlock.png deleted file mode 100644 index 5b736985..00000000 Binary files a/cvat/apps/engine/static/engine/icons/unlock.png and /dev/null differ diff --git a/cvat/apps/engine/static/engine/js/3rdparty.patch b/cvat/apps/engine/static/engine/js/3rdparty.patch deleted file mode 100644 index 7fb96693..00000000 --- a/cvat/apps/engine/static/engine/js/3rdparty.patch +++ /dev/null @@ -1,128 +0,0 @@ -From 11bd98c7f1ecf5a57b0eb327e6e86fa1e6ca33d9 Mon Sep 17 00:00:00 2001 -From: Boris Sekachev -Date: Tue, 25 Feb 2020 19:08:36 +0300 -Subject: [PATCH] Applied patch - ---- - .../static/engine/js/3rdparty/svg.draggable.js | 1 + - .../static/engine/js/3rdparty/svg.draw.js | 17 +++++++++++++++-- - .../static/engine/js/3rdparty/svg.resize.min.js | 2 +- - .../static/engine/js/3rdparty/svg.select.js | 5 ++++- - 4 files changed, 21 insertions(+), 4 deletions(-) - -diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js -index d88abf5..aba474c 100644 ---- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js -+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js -@@ -109,6 +109,7 @@ - - // while dragging - DragHandler.prototype.drag = function(e){ -+ this.m = this.el.node.getScreenCTM().inverse(); - - var box = this.getBBox() - , p = this.transformPoint(e) -diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js -index 68dbf2a..20a6917 100644 ---- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js -+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js -@@ -18,6 +18,7 @@ - this.startPoint = null; - this.lastUpdateCall = null; - this.options = {}; -+ this.set = new SVG.Set(); - - // Merge options and defaults - for (var i in this.el.draw.defaults) { -@@ -139,6 +140,8 @@ - // Call the calc-function which calculates the new position and size - this.calc(event); - -+ this.m = this.el.node.getScreenCTM().inverse(); -+ this.offset = { x: window.pageXOffset, y: window.pageYOffset }; - // Fire the `drawupdate`-event - this.el.fire('drawupdate', {event:event, p:this.p, m:this.m}); - }; -@@ -160,6 +163,16 @@ - this.el.fire('drawcancel'); - }; - -+ // Undo last drawed point -+ PaintHandler.prototype.undo = function () { -+ if (this.set.length()) { -+ this.set.members.splice(-1, 1)[0].remove(); -+ this.el.array().value.splice(-2, 1); -+ this.el.plot(this.el.array()); -+ this.el.fire('undopoint'); -+ } -+ }; -+ - // Calculate the corrected position when using `snapToGrid` - PaintHandler.prototype.snapToGrid = function (draw) { - -@@ -371,14 +384,14 @@ - - this.set.clear(); - -- for (var i = 0; i < array.length; ++i) { -+ for (var i = 0; i < array.length - 1; ++i) { - - this.p.x = array[i][0] - this.p.y = array[i][1] - - var p = this.p.matrixTransform(this.parent.node.getScreenCTM().inverse().multiply(this.el.node.getScreenCTM())); - -- this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y)); -+ this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y)).addClass("svg_draw_point"); - } - } - -diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.min.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.min.js -index 2dca34b..50ef3d3 100644 ---- a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.min.js -+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.min.js -@@ -1 +1 @@ --/*! svg.resize.js v1.4.3 MIT*/;!function(){"use strict";(function(){function t(t){t.remember("_resizeHandler",this),this.el=t,this.parameters={},this.lastUpdateCall=null,this.p=t.doc().node.createSVGPoint()}t.prototype.transformPoint=function(t,e,i){return this.p.x=t-(this.offset.x-window.pageXOffset),this.p.y=e-(this.offset.y-window.pageYOffset),this.p.matrixTransform(i||this.m)},t.prototype._extractPosition=function(t){return{x:null!=t.clientX?t.clientX:t.touches[0].clientX,y:null!=t.clientY?t.clientY:t.touches[0].clientY}},t.prototype.init=function(t){var e=this;if(this.stop(),"stop"!==t){this.options={};for(var i in this.el.resize.defaults)this.options[i]=this.el.resize.defaults[i],void 0!==t[i]&&(this.options[i]=t[i]);this.el.on("lt.resize",function(t){e.resize(t||window.event)}),this.el.on("rt.resize",function(t){e.resize(t||window.event)}),this.el.on("rb.resize",function(t){e.resize(t||window.event)}),this.el.on("lb.resize",function(t){e.resize(t||window.event)}),this.el.on("t.resize",function(t){e.resize(t||window.event)}),this.el.on("r.resize",function(t){e.resize(t||window.event)}),this.el.on("b.resize",function(t){e.resize(t||window.event)}),this.el.on("l.resize",function(t){e.resize(t||window.event)}),this.el.on("rot.resize",function(t){e.resize(t||window.event)}),this.el.on("point.resize",function(t){e.resize(t||window.event)}),this.update()}},t.prototype.stop=function(){return this.el.off("lt.resize"),this.el.off("rt.resize"),this.el.off("rb.resize"),this.el.off("lb.resize"),this.el.off("t.resize"),this.el.off("r.resize"),this.el.off("b.resize"),this.el.off("l.resize"),this.el.off("rot.resize"),this.el.off("point.resize"),this},t.prototype.resize=function(t){var e=this;this.m=this.el.node.getScreenCTM().inverse(),this.offset={x:window.pageXOffset,y:window.pageYOffset};var i=this._extractPosition(t.detail.event);if(this.parameters={type:this.el.type,p:this.transformPoint(i.x,i.y),x:t.detail.x,y:t.detail.y,box:this.el.bbox(),rotation:this.el.transform().rotation},"text"===this.el.type&&(this.parameters.fontSize=this.el.attr()["font-size"]),void 0!==t.detail.i){var s=this.el.array().valueOf();this.parameters.i=t.detail.i,this.parameters.pointCoords=[s[t.detail.i][0],s[t.detail.i][1]]}switch(t.type){case"lt":this.calc=function(t,e){var i=this.snapToGrid(t,e);if(this.parameters.box.width-i[0]>0&&this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x+i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize-i[0]);i=this.checkAspectRatio(i),this.el.move(this.parameters.box.x+i[0],this.parameters.box.y+i[1]).size(this.parameters.box.width-i[0],this.parameters.box.height-i[1])}};break;case"rt":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);if(this.parameters.box.width+i[0]>0&&this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x-i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize+i[0]);i=this.checkAspectRatio(i,!0),this.el.move(this.parameters.box.x,this.parameters.box.y+i[1]).size(this.parameters.box.width+i[0],this.parameters.box.height-i[1])}};break;case"rb":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.width+i[0]>0&&this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x-i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize+i[0]);i=this.checkAspectRatio(i),this.el.move(this.parameters.box.x,this.parameters.box.y).size(this.parameters.box.width+i[0],this.parameters.box.height+i[1])}};break;case"lb":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);if(this.parameters.box.width-i[0]>0&&this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x+i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize-i[0]);i=this.checkAspectRatio(i,!0),this.el.move(this.parameters.box.x+i[0],this.parameters.box.y).size(this.parameters.box.width-i[0],this.parameters.box.height+i[1])}};break;case"t":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);if(this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y+i[1]).height(this.parameters.box.height-i[1])}};break;case"r":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.width+i[0]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y).width(this.parameters.box.width+i[0])}};break;case"b":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y).height(this.parameters.box.height+i[1])}};break;case"l":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);if(this.parameters.box.width-i[0]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x+i[0],this.parameters.box.y).width(this.parameters.box.width-i[0])}};break;case"rot":this.calc=function(t,e){var i={x:t+this.parameters.p.x,y:e+this.parameters.p.y},s=Math.atan2(this.parameters.p.y-this.parameters.box.y-this.parameters.box.height/2,this.parameters.p.x-this.parameters.box.x-this.parameters.box.width/2),r=Math.atan2(i.y-this.parameters.box.y-this.parameters.box.height/2,i.x-this.parameters.box.x-this.parameters.box.width/2),a=this.parameters.rotation+180*(r-s)/Math.PI+this.options.snapToAngle/2;this.el.center(this.parameters.box.cx,this.parameters.box.cy).rotate(a-a%this.options.snapToAngle,this.parameters.box.cx,this.parameters.box.cy)};break;case"point":this.calc=function(t,e){var i=this.snapToGrid(t,e,this.parameters.pointCoords[0],this.parameters.pointCoords[1]),s=this.el.array().valueOf();s[this.parameters.i][0]=this.parameters.pointCoords[0]+i[0],s[this.parameters.i][1]=this.parameters.pointCoords[1]+i[1],this.el.plot(s)}}this.el.fire("resizestart",{dx:this.parameters.x,dy:this.parameters.y,event:t}),SVG.on(window,"touchmove.resize",function(t){e.update(t||window.event)}),SVG.on(window,"touchend.resize",function(){e.done()}),SVG.on(window,"mousemove.resize",function(t){e.update(t||window.event)}),SVG.on(window,"mouseup.resize",function(){e.done()})},t.prototype.update=function(t){if(!t)return void(this.lastUpdateCall&&this.calc(this.lastUpdateCall[0],this.lastUpdateCall[1]));var e=this._extractPosition(t),i=this.transformPoint(e.x,e.y),s=i.x-this.parameters.p.x,r=i.y-this.parameters.p.y;this.lastUpdateCall=[s,r],this.calc(s,r),this.el.fire("resizing",{dx:s,dy:r,event:t})},t.prototype.done=function(){this.lastUpdateCall=null,SVG.off(window,"mousemove.resize"),SVG.off(window,"mouseup.resize"),SVG.off(window,"touchmove.resize"),SVG.off(window,"touchend.resize"),this.el.fire("resizedone")},t.prototype.snapToGrid=function(t,e,i,s){var r;return void 0!==s?r=[(i+t)%this.options.snapToGrid,(s+e)%this.options.snapToGrid]:(i=null==i?3:i,r=[(this.parameters.box.x+t+(1&i?0:this.parameters.box.width))%this.options.snapToGrid,(this.parameters.box.y+e+(2&i?0:this.parameters.box.height))%this.options.snapToGrid]),t<0&&(r[0]-=this.options.snapToGrid),e<0&&(r[1]-=this.options.snapToGrid),t-=Math.abs(r[0])o.maxX&&(t=o.maxX-r),void 0!==o.minY&&a+eo.maxY&&(e=o.maxY-a),[t,e]},t.prototype.checkAspectRatio=function(t,e){if(!this.options.saveAspectRatio)return t;var i=t.slice(),s=this.parameters.box.width/this.parameters.box.height,r=this.parameters.box.width+t[0],a=this.parameters.box.height-t[1],o=r/a;return os&&(i[0]=this.parameters.box.width-a*s,e&&(i[0]=-i[0])),i},SVG.extend(SVG.Element,{resize:function(e){return(this.remember("_resizeHandler")||new t(this)).init(e||{}),this}}),SVG.Element.prototype.resize.defaults={snapToAngle:.1,snapToGrid:1,constraint:{},saveAspectRatio:!1}}).call(this)}(); -+/*! svg.resize.js v1.4.3 MIT*/;!function(){"use strict";(function(){function t(t){t.remember("_resizeHandler",this),this.el=t,this.parameters={},this.lastUpdateCall=null,this.p=t.doc().node.createSVGPoint()}t.prototype.transformPoint=function(t,e,i){return this.p.x=t-(this.offset.x-window.pageXOffset),this.p.y=e-(this.offset.y-window.pageYOffset),this.p.matrixTransform(i||this.m)},t.prototype._extractPosition=function(t){return{x:null!=t.clientX?t.clientX:t.touches[0].clientX,y:null!=t.clientY?t.clientY:t.touches[0].clientY}},t.prototype.init=function(t){var e=this;if(this.stop(),"stop"!==t){this.options={};for(var i in this.el.resize.defaults)this.options[i]=this.el.resize.defaults[i],void 0!==t[i]&&(this.options[i]=t[i]);this.el.on("lt.resize",function(t){e.resize(t||window.event)}),this.el.on("rt.resize",function(t){e.resize(t||window.event)}),this.el.on("rb.resize",function(t){e.resize(t||window.event)}),this.el.on("lb.resize",function(t){e.resize(t||window.event)}),this.el.on("t.resize",function(t){e.resize(t||window.event)}),this.el.on("r.resize",function(t){e.resize(t||window.event)}),this.el.on("b.resize",function(t){e.resize(t||window.event)}),this.el.on("l.resize",function(t){e.resize(t||window.event)}),this.el.on("rot.resize",function(t){e.resize(t||window.event)}),this.el.on("point.resize",function(t){e.resize(t||window.event)}),this.update()}},t.prototype.stop=function(){return this.el.off("lt.resize"),this.el.off("rt.resize"),this.el.off("rb.resize"),this.el.off("lb.resize"),this.el.off("t.resize"),this.el.off("r.resize"),this.el.off("b.resize"),this.el.off("l.resize"),this.el.off("rot.resize"),this.el.off("point.resize"),this},t.prototype.resize=function(t){if(!t.detail.event.button){var e=this;this.m=this.el.node.getScreenCTM().inverse(),this.offset={x:window.pageXOffset,y:window.pageYOffset};var i=this._extractPosition(t.detail.event);if(this.parameters={type:this.el.type,p:this.transformPoint(i.x,i.y),x:t.detail.x,y:t.detail.y,box:this.el.bbox(),rotation:this.el.transform().rotation},"text"===this.el.type&&(this.parameters.fontSize=this.el.attr()["font-size"]),void 0!==t.detail.i){var s=this.el.array().valueOf();this.parameters.i=t.detail.i,this.parameters.pointCoords=[s[t.detail.i][0],s[t.detail.i][1]]}switch(t.type){case"lt":this.calc=function(t,e){var i=this.snapToGrid(t,e);if(this.parameters.box.width-i[0]>0&&this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x+i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize-i[0]);i=this.checkAspectRatio(i),this.el.move(this.parameters.box.x+i[0],this.parameters.box.y+i[1]).size(this.parameters.box.width-i[0],this.parameters.box.height-i[1])}};break;case"rt":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);if(this.parameters.box.width+i[0]>0&&this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x-i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize+i[0]);i=this.checkAspectRatio(i,!0),this.el.move(this.parameters.box.x,this.parameters.box.y+i[1]).size(this.parameters.box.width+i[0],this.parameters.box.height-i[1])}};break;case"rb":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.width+i[0]>0&&this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x-i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize+i[0]);i=this.checkAspectRatio(i),this.el.move(this.parameters.box.x,this.parameters.box.y).size(this.parameters.box.width+i[0],this.parameters.box.height+i[1])}};break;case"lb":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);if(this.parameters.box.width-i[0]>0&&this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return this.el.move(this.parameters.box.x+i[0],this.parameters.box.y),void this.el.attr("font-size",this.parameters.fontSize-i[0]);i=this.checkAspectRatio(i,!0),this.el.move(this.parameters.box.x+i[0],this.parameters.box.y).size(this.parameters.box.width-i[0],this.parameters.box.height+i[1])}};break;case"t":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);if(this.parameters.box.height-i[1]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y+i[1]).height(this.parameters.box.height-i[1])}};break;case"r":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.width+i[0]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y).width(this.parameters.box.width+i[0])}};break;case"b":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);if(this.parameters.box.height+i[1]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x,this.parameters.box.y).height(this.parameters.box.height+i[1])}};break;case"l":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);if(this.parameters.box.width-i[0]>0){if("text"===this.parameters.type)return;this.el.move(this.parameters.box.x+i[0],this.parameters.box.y).width(this.parameters.box.width-i[0])}};break;case"rot":this.calc=function(t,e){var i={x:t+this.parameters.p.x,y:e+this.parameters.p.y},s=Math.atan2(this.parameters.p.y-this.parameters.box.y-this.parameters.box.height/2,this.parameters.p.x-this.parameters.box.x-this.parameters.box.width/2),r=Math.atan2(i.y-this.parameters.box.y-this.parameters.box.height/2,i.x-this.parameters.box.x-this.parameters.box.width/2),a=this.parameters.rotation+180*(r-s)/Math.PI+this.options.snapToAngle/2;this.el.center(this.parameters.box.cx,this.parameters.box.cy).rotate(a-a%this.options.snapToAngle,this.parameters.box.cx,this.parameters.box.cy)};break;case"point":this.calc=function(t,e){var i=this.snapToGrid(t,e,this.parameters.pointCoords[0],this.parameters.pointCoords[1]),s=this.el.array().valueOf();s[this.parameters.i][0]=this.parameters.pointCoords[0]+i[0],s[this.parameters.i][1]=this.parameters.pointCoords[1]+i[1],this.el.plot(s)}}this.el.fire("resizestart",{dx:this.parameters.x,dy:this.parameters.y,event:t}),SVG.on(window,"touchmove.resize",function(t){e.update(t||window.event)}),SVG.on(window,"touchend.resize",function(){e.done()}),SVG.on(window,"mousemove.resize",function(t){e.update(t||window.event)}),SVG.on(window,"mouseup.resize",function(){e.done()})}},t.prototype.update=function(t){if(!t)return void(this.lastUpdateCall&&this.calc(this.lastUpdateCall[0],this.lastUpdateCall[1]));this.m=this.el.node.getScreenCTM().inverse();var e=this._extractPosition(t),i=this.transformPoint(e.x,e.y),s=i.x-this.parameters.p.x,r=i.y-this.parameters.p.y;this.lastUpdateCall=[s,r],this.calc(s,r),this.el.fire("resizing",{dx:s,dy:r,event:t})},t.prototype.done=function(){this.lastUpdateCall=null,SVG.off(window,"mousemove.resize"),SVG.off(window,"mouseup.resize"),SVG.off(window,"touchmove.resize"),SVG.off(window,"touchend.resize"),this.el.fire("resizedone")},t.prototype.snapToGrid=function(t,e,i,s){var r;return void 0!==s?r=[(i+t)%this.options.snapToGrid,(s+e)%this.options.snapToGrid]:(i=null==i?3:i,r=[(this.parameters.box.x+t+(1&i?0:this.parameters.box.width))%this.options.snapToGrid,(this.parameters.box.y+e+(2&i?0:this.parameters.box.height))%this.options.snapToGrid]),t<0&&(r[0]-=this.options.snapToGrid),e<0&&(r[1]-=this.options.snapToGrid),t-=Math.abs(r[0])o.maxX&&(t=o.maxX-r),void 0!==o.minY&&a+eo.maxY&&(e=o.maxY-a),[t,e]},t.prototype.checkAspectRatio=function(t,e){if(!this.options.saveAspectRatio)return t;var i=t.slice(),s=this.parameters.box.width/this.parameters.box.height,r=this.parameters.box.width+t[0],a=this.parameters.box.height-t[1],o=r/a;return os&&(i[0]=this.parameters.box.width-a*s,e&&(i[0]=-i[0])),i},SVG.extend(SVG.Element,{resize:function(e){return(this.remember("_resizeHandler")||new t(this)).init(e||{}),this}}),SVG.Element.prototype.resize.defaults={snapToAngle:.1,snapToGrid:1,constraint:{},saveAspectRatio:!1}}).call(this)}(); -\ No newline at end of file -diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js -index 47e07bd..cee6d34 100644 ---- a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js -+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js -@@ -160,6 +160,7 @@ SelectHandler.prototype.drawPoints = function () { - ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; - ev.stopPropagation(); - -+ if (ev.which != 1) return false; - var x = ev.pageX || ev.touches[0].pageX; - var y = ev.pageY || ev.touches[0].pageY; - _this.el.fire('point', {x: x, y: y, i: k, event: ev}); -@@ -361,6 +362,7 @@ SelectHandler.prototype.cleanup = function () { - // stop watching the element, remove the selection - this.rectSelection.set.each(function () { - this.remove(); -+ SVG.off(this.node); - }); - - this.rectSelection.set.clear(); -@@ -371,6 +373,7 @@ SelectHandler.prototype.cleanup = function () { - // Remove all points, clear the set, stop watching the element - this.pointSelection.set.each(function () { - this.remove(); -+ SVG.off(this.node); - }); - - this.pointSelection.set.clear(); -@@ -379,8 +382,8 @@ SelectHandler.prototype.cleanup = function () { - - if (!this.pointSelection.isSelected && !this.rectSelection.isSelected) { - this.nested.remove(); -+ SVG.off(this.nested.node); - delete this.nested; -- - } - }; - --- -2.19.1 - diff --git a/cvat/apps/engine/static/engine/js/3rdparty/Decoder.worker.js b/cvat/apps/engine/static/engine/js/3rdparty/Decoder.worker.js deleted file mode 100644 index 23f3206a..00000000 --- a/cvat/apps/engine/static/engine/js/3rdparty/Decoder.worker.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e){var n={};function t(r){if(n[r])return n[r].exports;var a=n[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,t),a.l=!0,a.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var a in e)t.d(r,a,function(n){return e[n]}.bind(null,a));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=0)}([function(e,n,t){var r,a,i;a=[],void 0===(i="function"==typeof(r=function(){var e;!function(){(e=this)||("undefined"!=typeof window?e=window:"undefined"!=typeof self&&(e=self))}();var n=function(n,t){var r,a=void 0!==a?a:{},i={};for(r in a)a.hasOwnProperty(r)&&(i[r]=a[r]);var o,f=[],u=!1,s=!0,c="";(u||s)&&(s?c=self.location.href:document.currentScript&&(c=document.currentScript.src),c=0!==c.indexOf("blob:")?c.substr(0,c.lastIndexOf("/")+1):"",s&&(o=function(e){var n=new XMLHttpRequest;return n.open("GET",e,!1),n.responseType="arraybuffer",n.send(null),new Uint8Array(n.response)}));var d=a.print||console.log.bind(console),l=a.printErr||console.warn.bind(console);for(r in i)i.hasOwnProperty(r)&&(a[r]=i[r]);i=null,a.arguments&&(f=a.arguments),a.thisProgram&&a.thisProgram,a.quit&&a.quit;var p,y,h={"f64-rem":function(e,n){return e%n},debugger:function(){}};new Array(0),a.wasmBinary&&(p=a.wasmBinary),a.noExitRuntime&&a.noExitRuntime,"object"!=typeof WebAssembly&&l("no native wasm support detected");var v,m,g,b,w=new WebAssembly.Table({initial:10,maximum:10,element:"anyfunc"}),A=!1,M="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function P(e,n,t){for(var r=n+t,a=n;e[a]&&!(a>=r);)++a;if(a-n>16&&e.subarray&&M)return M.decode(e.subarray(n,a));for(var i="";n>10,56320|1023&s)}}else i+=String.fromCharCode((31&o)<<6|f)}else i+=String.fromCharCode(o)}return i}"undefined"!=typeof TextDecoder&&new TextDecoder("utf-16le");var D,S=a.TOTAL_MEMORY||104857600;function _(e){for(;e.length>0;){var n=e.shift();if("function"!=typeof n){var t=n.func;"number"==typeof t?void 0===n.arg?a.dynCall_v(t):a.dynCall_vi(t,n.arg):t(void 0===n.arg?null:n.arg)}else n()}}(y=a.wasmMemory?a.wasmMemory:new WebAssembly.Memory({initial:S/65536,maximum:S/65536}))&&(v=y.buffer),S=v.byteLength,v=D=v,a.HEAP8=m=new Int8Array(D),a.HEAP16=new Int16Array(D),a.HEAP32=b=new Int32Array(D),a.HEAPU8=g=new Uint8Array(D),a.HEAPU16=new Uint16Array(D),a.HEAPU32=new Uint32Array(D),a.HEAPF32=new Float32Array(D),a.HEAPF64=new Float64Array(D),b[2748]=5254064;var R=[],U=[],E=[],I=[],O=0,x=null,B=null;function C(e){throw a.onAbort&&a.onAbort(e),d(e+=""),l(e),A=!0,e="abort("+e+"). Build with -s ASSERTIONS=1 for more info.",new WebAssembly.RuntimeError(e)}a.preloadedImages={},a.preloadedAudios={};var H="data:application/octet-stream;base64,";function T(e){return String.prototype.startsWith?e.startsWith(H):0===e.indexOf(H)}var W,j="avc.wasm";function F(){try{if(p)return new Uint8Array(p);if(o)return o(j);throw"both async and sync fetching of the wasm failed"}catch(e){C(e)}}T(j)||(W=j,j=a.locateFile?a.locateFile(W,c):c+W),a.asm=function(){var e={env:V,wasi_unstable:V,global:{NaN:NaN,Infinity:1/0},"global.Math":Math,asm2wasm:h};function n(e,n){var t=e.exports;a.asm=t,function(e){if(O--,a.monitorRunDependencies&&a.monitorRunDependencies(O),0==O&&(null!==x&&(clearInterval(x),x=null),B)){var n=B;B=null,n()}}()}function t(e){n(e.instance)}function r(n){return(p||!u&&!s||"function"!=typeof fetch?new Promise((function(e,n){e(F())})):fetch(j,{credentials:"same-origin"}).then((function(e){if(!e.ok)throw"failed to load wasm binary file at '"+j+"'";return e.arrayBuffer()})).catch((function(){return F()}))).then((function(n){return WebAssembly.instantiate(n,e)})).then(n,(function(e){l("failed to asynchronously prepare wasm: "+e),C(e)}))}if(O++,a.monitorRunDependencies&&a.monitorRunDependencies(O),a.instantiateWasm)try{return a.instantiateWasm(e,n)}catch(e){return l("Module.instantiateWasm callback failed with error: "+e),!1}return function(){if(p||"function"!=typeof WebAssembly.instantiateStreaming||T(j)||"function"!=typeof fetch)return r(t);fetch(j,{credentials:"same-origin"}).then((function(n){return WebAssembly.instantiateStreaming(n,e).then(t,(function(e){l("wasm streaming compile failed: "+e),l("falling back to ArrayBuffer instantiation"),r(t)}))}))}(),{}};var N={buffers:[null,[],[]],printChar:function(e,n){var t=N.buffers[e];0===n||10===n?((1===e?d:l)(P(t,0)),t.length=0):t.push(n)},varargs:0,get:function(e){return N.varargs+=4,b[N.varargs-4>>2]},getStr:function(){var e,n;return(e=N.get())?P(g,e,n):""},get64:function(){var e=N.get();return N.get(),e},getZero:function(){N.get()}};function k(e,n,t,r){try{for(var a=0,i=0;i>2],f=b[n+(8*i+4)>>2],u=0;u>2]=a,0}catch(e){return"undefined"!=typeof FS&&e instanceof FS.ErrnoError||C(e),e.errno}}function z(){n()}function L(e,n,r){t(e,n,r)}a._broadwayOnHeadersDecoded=z,a._broadwayOnPictureDecoded=L;var q,G,V={g:function(){return k.apply(null,arguments)},__memory_base:1024,__table_base:0,f:z,e:L,b:function(){return m.length},d:function(e,n,t){g.set(g.subarray(n,n+t),e)},a:function(e){C("OOM")},c:C,memory:y,table:w},X=a.asm({},V,v);function Y(e){function n(){q||(q=!0,A||(_(U),_(E),a.onRuntimeInitialized&&a.onRuntimeInitialized(),function(){if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;)e=a.postRun.shift(),I.unshift(e);var e;_(I)}()))}e=e||f,O>0||(function(){if(a.preRun)for("function"==typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)e=a.preRun.shift(),R.unshift(e);var e;_(R)}(),O>0||(a.setStatus?(a.setStatus("Running..."),setTimeout((function(){setTimeout((function(){a.setStatus("")}),1),n()}),1)):n()))}if(a.asm=X,a._broadwayCreateStream=function(){return a.asm.h.apply(null,arguments)},a._broadwayExit=function(){return a.asm.i.apply(null,arguments)},a._broadwayGetMajorVersion=function(){return a.asm.j.apply(null,arguments)},a._broadwayGetMinorVersion=function(){return a.asm.k.apply(null,arguments)},a._broadwayInit=function(){return a.asm.l.apply(null,arguments)},a._broadwayPlayStream=function(){return a.asm.m.apply(null,arguments)},a.asm=X,B=function e(){q||Y(),q||(B=e)},a.run=Y,a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);a.preInit.length>0;)a.preInit.pop()();Y(),void 0!==e&&e.Module&&(G=e.Module),void 0!==a&&(G=a),G._broadwayOnHeadersDecoded=n,G._broadwayOnPictureDecoded=t;var Z,J=!1;return G.onRuntimeInitialized=function(){J=!0,Z&&Z(G)},function(e){J?e(G):Z=e}};return function(){"use strict";var t=function(){return(new Date).getTime()};"undefined"!=typeof performance&&performance.now&&(t=function(){return performance.now()});var r=function(e){var r;this.options=e||{},this.now=t;var a,o,f=function(e,n,o){var f,u=this.pictureBuffers[e];u||(u=this.pictureBuffers[e]=a(e,n*o*3/2));var s=!1;if(this.infoAr.length&&(s=!0,f=this.infoAr),this.infoAr=[],this.options.rgb){r||(r=i(n,o)),r.inp.set(u),r.doit();var c=new Uint8Array(r.outSize);return c.set(r.out),s&&(f[0].finishDecoding=t()),void this.onPictureDecoded(c,n,o,f)}s&&(f[0].finishDecoding=t()),this.onPictureDecoded(u,n,o,f)}.bind(this);this.options.sliceMode&&(f=function(e,n,r,i){var f=this.pictureBuffers[e];f||(f=this.pictureBuffers[e]=a(e,n*r*3/2));var u,s=this.pictureBuffers[i];s||(s=this.pictureBuffers[i]=o(i,18)),this.infoAr.length&&(u=this.infoAr),this.infoAr=[],u[0].finishDecoding=t();for(var c=[],d=0;d<20;++d)c.push(s[d]);u[0].sliceInfoAr=c,this.onPictureDecoded(f,n,r,u)}.bind(this));var u=n.apply({},[function(){},f]),s=this;this.onPictureDecoded=function(e,n,t,r){},this.onDecoderReady=function(){};var c=[];this.decode=function(e,n,t){c.push([e,n,t])},u((function(e){e.HEAP8;var n=e.HEAPU8;e.HEAP16,e.HEAP32,e._broadwayInit(),a=function(e,t){return n.subarray(e,e+t)},o=function(e,t){return new Uint32Array(n.buffer,e,t)},s.streamBuffer=a(e._broadwayCreateStream(1048576),1048576),s.pictureBuffers={},s.infoAr=[];var r=0;if(s.options.sliceMode?(r=s.options.sliceNum,s.decode=function(n,a,i){s.infoAr.push(a),a.startDecoding=t();var o,f=a.nals;if(!f){f=[],a.nals=f;var u=n.length,c=!1,d=0,l=0;for(o=0;o>2,f=i+o+o,u=n*t*4,s=f+u+4*Math.pow(2,24),c=Math.pow(2,24),d=c;d>2;m=r(r(l,p)|0,4)|0;v=(y+h|0)+h|0;b=0;g=b+m|0;A=g+v|0;a=~~+o(+2,+24);a=r(a,4)|0;for(t=0|0;(t|0)<(a|0)|0;t=t+4|0){d[(A+t|0)>>2]=0}}function P(){var e=0;var n=0;var t=0;var r=0;var a=0;var i=0;var o=0;var f=0;var u=0;var c=0;var v=0;var m=0;var M=0;var P=0;M=b|0;e=g|0;n=e+y|0|0;t=n+h|0;for(u=0;(u|0)<(p|0);u=u+2|0){v=n;m=t;for(c=0;(c|0)<(l|0);c=c+2|0){r=s[e>>0]|0;a=s[(e+l|0)>>0]|0;i=s[n>>0]|0;o=s[t>>0]|0;P=((r<<16|0)+(i<<8|0)|0)+o|0;f=d[(A>>2)+P|0]|0;if(f){}else{f=D(r,i,o)|0;d[(A>>2)+P|0]=f|0}d[M>>2]=f;P=((a<<16|0)+(i<<8|0)|0)+o|0;f=d[(A>>2)+P|0]|0;if(f){}else{f=D(a,i,o)|0;d[(A>>2)+P|0]=f|0}d[(M+w|0)>>2]=f;M=M+4|0;e=e+1|0;r=s[e>>0]|0;a=s[(e+l|0)>>0]|0;P=((r<<16|0)+(i<<8|0)|0)+o|0;f=d[(A>>2)+P|0]|0;if(f){}else{f=D(r,i,o)|0;d[(A>>2)+P|0]=f|0}d[M>>2]=f;P=((a<<16|0)+(i<<8|0)|0)+o|0;f=d[(A>>2)+P|0]|0;if(f){}else{f=D(a,i,o)|0;d[(A>>2)+P|0]=f|0}d[(M+w|0)>>2]=f;M=M+4|0;e=e+1|0;n=n+1|0;t=t+1|0}M=M+w|0;e=e+l|0}}function D(e,n,t){e=e|0;n=n|0;t=t|0;var o=0;var f=0;var u=0;var s=0;var c=0;var d=0;var l=0;var p=0;var y=0;c=r(1192,e-16|0)|0;d=r(1634,t-128|0)|0;l=r(832,t-128|0)|0;p=r(400,n-128|0)|0;y=r(2066,n-128|0)|0;o=(c+d|0)>>10|0;f=((c-l|0)-p|0)>>10|0;u=(c+y|0)>>10|0;if((o&255|0)!=(o|0)|0){o=a(255,i(0,o|0)|0)|0}if((f&255|0)!=(f|0)|0){f=a(255,i(0,f|0)|0)|0}if((u&255|0)!=(u|0)|0){u=a(255,i(0,u|0)|0)|0}s=255;s=s<<8|0;s=s+u|0;s=s<<8|0;s=s+f|0;s=s<<8|0;s=s+o|0;return s|0}return{init:M,doit:P}}(e,{},l);return p.init(n,t),a[r]=p,p.heap=l,p.out=new Uint8Array(l,0,u),p.inp=new Uint8Array(l,u,f),p.outSize=u,p};if("undefined"!=typeof self){var o,f,u,s,c,d,l=!1,p=!1,y=!1,h=0,v=0,m=0,g=0,b=[],w=[],A=function(e){if(w.length){for(var n=w.shift();n&&n.byteLength!==e;)n=w.shift();if(n)return n}return new ArrayBuffer(e)},M=function(e,n,t,r,a){var i=function(t,a){var i=0;for(i=0;i<16;++i){var o=t+r*i,f=a+r*i;n.set(e.subarray(o,f),o)}},o=function(t,a){var i=0;for(i=0;i<8;++i){var o=t+r/2*i,f=a+r/2*i;n.set(e.subarray(o,f),o)}},f=function(t,r){n.set(e.subarray(t,r),t)},u=t[0],s=t[1];s>0&&(i(u,s),o(t[2],t[3]),o(t[4],t[5])),u=t[6],(s=t[7])>0&&(i(u,s),o(t[8],t[9]),o(t[10],t[11])),u=t[12],(s=t[15])>0&&(f(u,s),f(t[13],t[16]),f(t[14],t[17]))},P=function(e){m=(v=e)-1};self.addEventListener("message",(function(e){if(l){if(p&&e.data.reuse&&w.push(e.data.reuse),e.data.buf)return void(y&&0!==g?b.push(e.data):o.decode(new Uint8Array(e.data.buf,e.data.offset||0,e.data.length),e.data.info,(function(){y&&h!==m&&postMessage(e.data,[e.data.buf])})));if(e.data.slice){var n=t();if(M(new Uint8Array(e.data.slice),u,e.data.infos[0].sliceInfoAr,e.data.width,e.data.height),e.data.theOne&&(M(u,new Uint8Array(e.data.slice),f,e.data.width,e.data.height),d>e.data.infos[0].timeDecoding&&(e.data.infos[0].timeDecoding=d),e.data.infos[0].timeCopy+=t()-n),postMessage(e.data,[e.data.slice]),0==(g-=1)&&b.length){var a=b.shift();o.decode(new Uint8Array(a.buf,a.offset||0,a.length),a.info,(function(){y&&h!==m&&postMessage(a,[a.buf])}))}return}if(e.data.setSliceCnt)return void P(e.data.sliceCnt)}else e.data&&"Broadway.js - Worker init"===e.data.type&&(l=!0,o=new r(e.data.options),e.data.options.sliceMode?(p=!0,y=!0,h=e.data.options.sliceNum,P(e.data.options.sliceCnt),o.onPictureDecoded=function(e,n,t,r){var a=new Uint8Array(A(e.length));M(e,a,r[0].sliceInfoAr,n),s=r[0].startDecoding,c=r[0].finishDecoding,d=c-s,r[0].timeDecoding=d,r[0].timeCopy=0,postMessage({slice:a.buffer,sliceNum:h,width:n,height:t,infos:r},[a.buffer]),g=v-1,u=e,f=r[0].sliceInfoAr}):e.data.options.reuseMemory?(p=!0,o.onPictureDecoded=function(e,n,t,r){var a=new Uint8Array(A(e.length));a.set(e,0,e.length),postMessage({buf:a.buffer,length:e.length,width:n,height:t,infos:r},[a.buffer])}):o.onPictureDecoded=function(e,n,t,r){e&&(e=new Uint8Array(e));var a=new Uint8Array(e.length);a.set(e,0,e.length),postMessage({buf:a.buffer,length:e.length,width:n,height:t,infos:r},[a.buffer])},postMessage({consoleLog:"broadway worker initialized"}))}),!1)}return r.nowValue=t,r}()})?r.apply(n,a):r)||(e.exports=i)}]); -//# sourceMappingURL=Decoder.worker.js.map \ No newline at end of file diff --git a/cvat/apps/engine/static/engine/js/3rdparty/avc.wasm b/cvat/apps/engine/static/engine/js/3rdparty/avc.wasm deleted file mode 100644 index 559b2b31..00000000 Binary files a/cvat/apps/engine/static/engine/js/3rdparty/avc.wasm and /dev/null differ diff --git a/cvat/apps/engine/static/engine/js/3rdparty/defiant.min.js b/cvat/apps/engine/static/engine/js/3rdparty/defiant.min.js deleted file mode 100644 index 20de25e7..00000000 --- a/cvat/apps/engine/static/engine/js/3rdparty/defiant.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/* - * defiant.js [v2.2.3] - * http://www.defiantjs.com - * Copyright (c) 2013-2019 Hakan Bilgin - * License GNU AGPLv3 - */if(function(window,module){"use strict";var defiant={is_ie:/(msie|trident)/i.test(navigator.userAgent),is_safari:/safari/i.test(navigator.userAgent),env:"production",xml_decl:'',namespace:'xmlns:d="defiant-namespace"',tabsize:4,snapshots:{},node:{},renderXml:function(e,t){var n=new window.XSLTProcessor,r=document.createElement("span"),a='//xsl:template[@name="'+e+'"]',s=this.node.selectSingleNode(this.xsl_template,a);return(s=this.node.selectSingleNode(this.xsl_template,a)).setAttribute("match","/"),n.importStylesheet(this.xsl_template),r.appendChild(n.transformToFragment(t,document)),s.removeAttribute("match"),r.innerHTML},render:function(e,t){var n,r,a,s,o=new window.XSLTProcessor,i=document.createElement("span"),l={match:"/"};switch(typeof e){case"object":this.extend(l,e),l.data||(l.data=t);break;case"string":l.template=e,l.data=t;break;default:throw"error"}if(l.data=l.data.nodeType?l.data:defiant.json.toXML(l.data),n='//xsl:template[@name="'+l.template+'"]',this.xsl_template||this.gatherTemplates(),l.sorter&&(s=this.node.selectSingleNode(this.xsl_template,n+"//xsl:for-each//xsl:sort"))&&(l.sorter.order&&s.setAttribute("order",l.sorter.order),l.sorter.select&&s.setAttribute("select",l.sorter.select),s.setAttribute("data-type",l.sorter.type||"text")),(a=this.node.selectSingleNode(this.xsl_template,n)).setAttribute("match",l.match),o.importStylesheet(this.xsl_template),i.appendChild(o.transformToFragment(l.data,document)),a.removeAttribute("match"),this.is_safari)for(var c=0,d=(r=i.getElementsByTagName("script")).length;c"+t.replace(/defiant:(\w+)/g,"$1")+"")},registerTemplate:function(e){this.xsl_template=this.xmlFromString('"+e.replace(/defiant:(\w+)/g,"$1")+"")},getSnapshot:function(e,t){return this.json.toXML(e,t||!0)},createSnapshot:function(e,t){var n=this,r="snapshot_"+Date.now();this.json.toXML(e,function(e){n.snapshots[r]=e,t(r)})},getFacets:function(e,t){var n,r,a,s,o,i,l=e.constructor===String&&"snapshot_"===e.slice(0,9)?this.snapshots[e].doc:defiant.json.toXML(e),c=l.cloneNode(!0),d={},u={},p=0,h=function(e){var t=e.childNodes.length;switch(e.nodeType){case 1:t>=p&&(p=t,r=e);case 9:e.childNodes.map(function(e){return h(e)})}};for(i in h(l),r.childNodes.map(function(e){u[e.nodeName]||(u[e.nodeName]=1),u[e.nodeName]++}),p=0,u)p<=u[i]&&(p=u[i],o=i);return this.createFacetTemplate(t),s=defiant.node.selectSingleNode(c,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]'),defiant.node.selectNodes(c,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]/'+o).map(function(e){return e.parentNode.removeChild(e)}),a=defiant.node.selectNodes(l,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]/'+o),n=a.length-1,a.map(function(e,t){if(s.appendChild(e.cloneNode(!0)),t%50==49||t===n){var a=defiant.render("facets",c).replace(/\n|\t/g,"").replace(/"": 0,?/g,"").replace(/,\}/g,"}"),i=JSON.parse(a);d=defiant.concatFacet(i,d),defiant.node.selectNodes(c,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]/'+o).map(function(e){return e.parentNode.removeChild(e)})}}),d},createFacetTemplate:function(e){var t,n,r=[],a=[];for(n in e)r.push(''),a.push('"'+n+'": {"": '+',}'.replace(/\n|\t/g,""));t=r.join("")+'{'+a.join(",")+"}",this.registerTemplate(t)},xmlFromString:function(e){var t;return null===(e=e.replace(/>\s{1,}<")).trim().match(/<\?xml/)&&(e=this.xml_decl+e),"ActiveXObject"in window?((t=new ActiveXObject("Msxml2.DOMDocument")).loadXML(e),t.setProperty("SelectionNamespaces",this.namespace),-1===e.indexOf("xsl:stylesheet")&&t.setProperty("SelectionLanguage","XPath")):t=(new DOMParser).parseFromString(e,"text/xml"),t},concatFacet:function(e,t){for(var n in t)e[n]&&"object"==typeof t[n]?this.concatFacet(e[n],t[n]):e[n]=(e[n]||0)+t[n];return e},extend:function(e,t){for(var n in t)e[n]&&"object"==typeof t[n]?this.extend(e[n],t[n]):e[n]=t[n];return e},node:{selectNodes:function(e,t){if(e.evaluate){for(var n=e.createNSResolver(e.documentElement),r=e.evaluate(t,e,n,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null),a=[],s=0,o=r.snapshotLength;s0?n[0]:null}return e.selectSingleNode(t)},prettyPrint:function(e){var t,n=defiant,r=n.tabsize,a=n.xml_decl.toLowerCase();t=n.is_ie?e.xml:(new XMLSerializer).serializeToString(e),"development"!==n.env&&(t=t.replace(/ \w+\:d=".*?"| d\:\w+=".*?"/g,""));for(var s,o,i=t.trim().replace(/(>)\s*(<)(\/*)/g,"$1\n$2$3").split("\n"),l=-1,c=0,d=i.length;c/g),o=null!==i[c].match(/<\/[\w\:]+>/g),null!==i[c].match(/<.*?\/>/g)&&(s=o=!0),s&&l++,i[c]=String().fill(l,"\t")+i[c],s&&o&&l--,!s&&o&&l--);return i.join("\n").replace(/\t/g,String().fill(r," "))},toJSON:function(e,t){var n=function(e){var t,r,a,s,o,i,l,c,d,u,p={},h=window;switch(e.nodeType){case 1:for("Array"===(o=e.getAttribute("d:constr"))?p=[]:"String"===o&&""===e.textContent&&(p=""),c=0,d=(t=e.attributes).length;c/,rx_constructor:/<(.+?)( d:contr=".*?")>/,rx_namespace:/ xmlns\:d="defiant\-namespace"/,rx_data:/(<.+?>)(.*?)(<\/d:data>)/i,rx_function:/function (\w+)/i,namespace:'xmlns:d="defiant-namespace"',to_xml_str:function(e){return{str:this.hash_to_xml(null,e),map:this.map}},hash_to_xml:function(e,t,n){var r,a,s,o,i,l,c,d,u,p=t.constructor===Array,h=this,m=[],f=[],g=function(t,r){if(null!==(a=r[t])&&void 0!==a&&"NaN"!==a.toString()||(a=null),o="@"===t.slice(0,1),(i=n?e:t)==+i&&r.constructor!==Object&&(i="d:item"),null===a?(l=null,c=!1):(l=a.constructor,c=l.toString().match(h.rx_function)[1]),o)f.push(i.slice(1)+'="'+h.escape_xml(a)+'"'),"String"!==c&&f.push("d:"+i.slice(1)+'="'+c+'"');else if(null===a)m.push(h.scalar_to_xml(i,a));else switch(l){case Function:throw"JSON data should not contain functions. Please check your structure.";case Object:m.push(h.hash_to_xml(i,a));break;case Array:if(t===i){if(s=a.constructor===Array)for(d=a.length;d--;)null!==a[d]&&a[d]&&a[d].constructor!==Array||(s=!0),s||a[d].constructor!==Object||(s=!0);m.push(h.scalar_to_xml(i,a,s));break}case String:if("string"==typeof a&&(a=a.toString().replace(/\&/g,"&").replace(/\r|\n/g," ")),"#text"===i){h.map.push(r),f.push('d:mi="'+h.map.length+'"'),f.push('d:constr="'+c+'"'),m.push(h.escape_xml(a));break}case Number:case Boolean:if("#text"===i&&"String"!==c){h.map.push(r),f.push('d:mi="'+h.map.length+'"'),f.push('d:constr="'+c+'"'),m.push(h.escape_xml(a));break}m.push(h.scalar_to_xml(i,a))}};if(t.constructor===Array)for(d=0,u=t.length;d"+m.join("")+"":"/>"))},scalar_to_xml:function(e,t,n){var r,a,s,o="";if(null===e.match(this.rx_validate_name)&&(o+=' d:name="'+e+'"',e="d:name",n=!1),null!==t&&"NaN"!==t.toString()||(t=null),null===t)return"<"+e+' d:constr="null"/>';if(1===t.length&&t.constructor===Array&&!t[0])return"<"+e+' d:constr="null" d:type="ArrayItem"/>';if(1===t.length&&t[0].constructor===Object){var i=(r=this.hash_to_xml(!1,t[0])).match(this.rx_node),l=r.match(this.rx_constructor);return"<"+e+(i=null!==i?i[2].replace(this.rx_namespace,"").replace(/>/,"").replace(/"\/$/,'"'):"")+" "+(l=null!==l?l[2]:"")+' d:type="ArrayItem">'+(r=null!==(r=r.match(this.rx_data))?r[2]:"")+""}return 0===t.length&&t.constructor===Array?"<"+e+' d:constr="Array"/>':n?this.hash_to_xml(e,t,!0):(s=(a=t.constructor).toString().match(this.rx_function)[1],r=a===Array?this.hash_to_xml("d:item",t,!0):this.escape_xml(t),o+=' d:constr="'+s+'"',this.map.push(t),o+=' d:mi="'+this.map.length+'"',"#text"===e?this.escape_xml(t):"<"+e+o+">"+r+"")},escape_xml:function(e){return String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/ /g," ")}},toXML:function(e,t){var n,r,a=defiant.json.interpreter;switch(typeof t){case"function":return void defiant.compiled.to_xml_str(e,function(n){t({doc:defiant.xmlFromString(n.str),src:e,map:n.map})});case"boolean":return n=a.to_xml_str.call(a,e),{doc:defiant.xmlFromString(n.str),src:e,map:n.map};default:return n=a.to_xml_str.call(a,e),r=defiant.xmlFromString(n.str),this.search.map=n.map,r}},search:function(e,t,n){e.constructor===String&&"snapshot_"===e.slice(0,9)&&defiant.snapshots[e]&&(e=defiant.snapshots[e]);var r,a,s=defiant.json,o=e.doc&&e.doc.nodeType,i=o?e.doc:s.toXML(e),l=o?e.map:s.search.map,c=o?e.src:e,d=defiant.node[n?"selectSingleNode":"selectNodes"](i,t.xTransform()),u=[];for(n&&(d=[d]),a=d.length;a--;)switch(d[a].nodeType){case 2:case 3:u.unshift(d[a].nodeValue);break;default:r=+d[a].getAttribute("d:mi"),u.unshift(l[r-1])}return"development"===defiant.env&&(u.trace=s.matchTrace(c,u,d)),u},matchTrace:function(e,t,n){var r=[],a=0,s=window,o=defiant.node.toJSON,i=function(e){return JSON.stringify(e,null,"\t").replace(/\t/g,"")},l=i(e);return n.map(function(e,c){var d,u,p,h,m,f,g,x=0;switch(e.nodeType){case 2:d=n[c].ownerElement?n[c].ownerElement.getAttribute("d:"+n[c].nodeName):"String",h=s[d](t[c]),m='"@'+n[c].nodeName+'": '+h,f=l.indexOf(m,a);break;case 3:d=n[c].parentNode.getAttribute("d:constr"),h=s[d](t[c]),m='"'+n[c].parentNode.nodeName+'": '+("Number"===m?h:'"'+h+'"'),f=l.indexOf(m,a);break;default:d=e.getAttribute("d:constr"),["String","Number"].indexOf(d)>-1?(u=o(n[c].parentNode),p=i(u),h=s[d](t[c]),m='"'+n[c].nodeName+'": '+("Number"===d?h:'"'+h+'"'),f=l.indexOf(p,a)+p.indexOf(m)):(m=i(t[c]),f=l.indexOf(m),x=m.split("\n").length-1)}a=f+1,g=l.slice(0,f).split("\n").length,r.push([g,x])}),r}}},x10={id:1,work_handler:function(e){var t=Array.prototype.slice.call(e.data,2),n=e.data[0],r=e.data[1],a=tree[n].apply(tree,t);a.map=JSON.parse(JSON.stringify(a.map)),postMessage([r,n,a])},setup:function(e){var t=window.URL||window.webkitURL,n="var tree = {"+this.parse(e).join(",")+"};",r=new Blob([n+'self.addEventListener("message", '+this.work_handler.toString()+", false);"],{type:"text/javascript"}),a=new Worker(t.createObjectURL(r));return a.onmessage=function(e){var t=Array.prototype.slice.call(e.data,2),n=e.data[0],r=e.data[1];x10.observer.emit("x10:"+r+n,t),x10.observer.off("x10:"+r+n)},a},call_handler:function(e,t){return function(){var n=Array.prototype.slice.call(arguments,0,-1),r=arguments[arguments.length-1],a=x10.id++;n.unshift(a),n.unshift(e),x10.observer.on("x10:"+e+a,function(e){r(e.detail[0])}),t.postMessage(n)}},compile:function(e){var t,n=this.setup("function"==typeof e?{func:e}:e),r={};if("function"==typeof e)return r.func=this.call_handler("func",n),r.func;for(t in e)r[t]=this.call_handler(t,n);return r},parse:function(e,t){var n,r,a,s=[];for(n in e)if(null!==(a=e[n]))if(void 0!==a){switch(a.constructor){case Date:r="new Date("+a.valueOf()+")";break;case Object:r="{"+this.parse(a).join(",")+"}";break;case Array:r="["+this.parse(a,!0).join(",")+"]";break;case String:r='"'+a.replace(/"/g,'\\"')+'"';break;case RegExp:case Function:r=a.toString();break;default:r=a}t?s.push(r):s.push(n+":"+r)}else s.push(n+":undefined");else s.push(n+":null");return s},observer:(stack={},{on:function(e,t){stack[e]||(stack[e]=[]),stack[e].unshift(t)},off:function(e,t){if(stack[e]){var n=stack[e].indexOf(t);stack[e].splice(n,1)}},emit:function(e,t){if(stack[e])for(var n={type:e,detail:t,isCanceled:!1,cancelBubble:function(){this.isCanceled=!0}},r=stack[e].length;r--;){if(n.isCanceled)return;stack[e][r](n)}}})},stack;String.prototype.fill||(String.prototype.fill=function(e,t){var n=this;for(t=t||" ";n.length elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - - - - var preservedScriptAttributes = { - type: true, - src: true, - noModule: true - }; - - function DOMEval( code, doc, node ) { - doc = doc || document; - - var i, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - if ( node[ i ] ) { - script[ i ] = node[ i ]; - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.3.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.3 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-08-08 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( el ) { - el.className = "i"; - return !el.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID filter and find - if ( support.getById ) { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( el ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( el ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - jQuery.contains( elem.ownerDocument, elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // Support: IE <=9 only - option: [ 1, "" ], - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 only -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - div.style.position = "absolute"; - scrollboxSizeVal = div.offsetWidth === 36 || "absolute"; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property -function vendorPropName( name ) { - - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. -function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; - } - return ret; -} - -function setPositiveNumber( elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - ) ); - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - val = curCSS( elem, dimension, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox; - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = valueIsBorderBox && - ( support.boxSizingReliable() || val === elem.style[ dimension ] ); - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - if ( val === "auto" || - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) { - - val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ]; - - // offsetWidth/offsetHeight provide border-box values - valueIsBorderBox = true; - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra && boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ); - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && support.scrollboxSize() === styles.position ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = Date.now(); - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - - -jQuery._evalUrl = function( url ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - "throws": true - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - - - - - {% for js_file in js_3rdparty %} - - {% endfor %} -{% endblock %} - - -{% block head_js_cvat %} - {{ block.super }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{% endblock %} - - -{% block content %} -
-
-
-
- - - - - - - - - - - - - - - -
    -
  • Copy Object URL
  • -
  • Change Color
  • -
  • Remove Shape
  • -
  • Switch Occluded
  • -
  • Switch Lock
  • -
  • Split
  • -
  • Enable Dragging
  • -
  • Reset Perspective
  • -
  • Switch Perspective Orientation
  • - -
- -
    -
  • Copy Job URL
  • -
  • Copy Frame URL
  • -
- -
    -
  • Remove
  • -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - -
- - -
-
- - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
- - - - - - -
- - -
- - -
-
-
- -
- - - - - - -
- - -
-
- -
-{% endblock %} diff --git a/cvat/apps/engine/templates/engine/base.html b/cvat/apps/engine/templates/engine/base.html deleted file mode 100644 index 56415d88..00000000 --- a/cvat/apps/engine/templates/engine/base.html +++ /dev/null @@ -1,90 +0,0 @@ - - -{% load static compress %} - - - - - - {% block head_title %} - Computer Vision Annotation Tool (CVAT) - {% endblock %} - - - {% compress css %} - {% block head_css %} - - {% endblock %} - {% endcompress %} - - {% compress js file platformchecker %} - - - {% endcompress %} - - {% compress js file thirdparty %} - {% block head_js_3rdparty %} - - - - {% endblock %} - {% endcompress %} - - {% compress js file cvat %} - {% block head_js_cvat %} - - - {% endblock %} - {% endcompress %} - - - {% block header %} - {% endblock %} - - {% block content %} - {% endblock %} - - {% block footer %} - {% endblock %} - - - LOADING - - - - - - - - - - diff --git a/cvat/apps/engine/tests/_test_rest_api.py b/cvat/apps/engine/tests/_test_rest_api.py index 46c6f3e5..73c81141 100644 --- a/cvat/apps/engine/tests/_test_rest_api.py +++ b/cvat/apps/engine/tests/_test_rest_api.py @@ -552,8 +552,8 @@ class UserAPITestCase(APITestCase): self.assertEqual(data["username"], user.username) self.assertEqual(data["first_name"], user.first_name) self.assertEqual(data["last_name"], user.last_name) - self.assertEqual(data["email"], user.email) extra_check = self.assertIn if is_full else self.assertNotIn + extra_check("email", data) extra_check("groups", data) extra_check("is_staff", data) extra_check("is_superuser", data) @@ -1988,7 +1988,7 @@ class JobAnnotationAPITestCase(APITestCase): "name": "parked", "mutable": True, "input_type": "checkbox", - "default_value": False + "default_value": "false" }, ] }, @@ -2100,6 +2100,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [] } ], @@ -2108,6 +2109,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2126,6 +2128,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 300.222, 400, 500, 1, 3], "type": "polygon", @@ -2137,6 +2140,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2171,6 +2175,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2220,6 +2225,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [] } ], @@ -2228,6 +2234,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2240,16 +2247,17 @@ class JobAnnotationAPITestCase(APITestCase): ], "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", - "occluded": False + "occluded": False, }, { "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 300.222, 400, 500, 1, 3], "type": "polygon", - "occluded": False + "occluded": False, }, ], "tracks": [ @@ -2257,6 +2265,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2283,7 +2292,7 @@ class JobAnnotationAPITestCase(APITestCase): "points": [2.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": True, - "outside": True + "outside": True, }, ] }, @@ -2291,6 +2300,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2299,7 +2309,7 @@ class JobAnnotationAPITestCase(APITestCase): "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": False, - "outside": False + "outside": False, } ] }, @@ -2361,7 +2371,8 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": 11010101, "group": None, - "attributes": [] + "source": "manual", + "attributes": [], } ], "shapes": [ @@ -2369,6 +2380,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": 32234234, @@ -2381,16 +2393,17 @@ class JobAnnotationAPITestCase(APITestCase): ], "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", - "occluded": False + "occluded": False, }, { "frame": 1, "label_id": 1212121, "group": None, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 300.222, 400, 500, 1, 3], "type": "polygon", - "occluded": False + "occluded": False, }, ], "tracks": [ @@ -2398,6 +2411,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 0, "label_id": 0, "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2423,7 +2437,7 @@ class JobAnnotationAPITestCase(APITestCase): "points": [2.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": True, - "outside": True + "outside": True, }, ] }, @@ -2431,6 +2445,7 @@ class JobAnnotationAPITestCase(APITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2439,7 +2454,7 @@ class JobAnnotationAPITestCase(APITestCase): "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": False, - "outside": False + "outside": False, } ] }, @@ -2574,7 +2589,8 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, - "attributes": [] + "source": "manual", + "attributes": [], } ], "shapes": [ @@ -2582,6 +2598,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2594,16 +2611,17 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): ], "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", - "occluded": False + "occluded": False, }, { "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 300.222, 400, 500, 1, 3], "type": "polygon", - "occluded": False + "occluded": False, }, ], "tracks": [ @@ -2611,6 +2629,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2637,7 +2656,8 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [2.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": True, - "outside": True + "outside": True, + }, ] }, @@ -2645,6 +2665,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2653,7 +2674,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": False, - "outside": False + "outside": False, } ] }, @@ -2694,7 +2715,8 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, - "attributes": [] + "source": "manual", + "attributes": [], } ], "shapes": [ @@ -2702,6 +2724,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2714,16 +2737,17 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): ], "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", - "occluded": False + "occluded": False, }, { "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 300.222, 400, 500, 1, 3], "type": "polygon", - "occluded": False + "occluded": False, }, ], "tracks": [ @@ -2731,6 +2755,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2757,7 +2782,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [2.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": True, - "outside": True + "outside": True, }, ] }, @@ -2765,6 +2790,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2773,7 +2799,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": False, - "outside": False + "outside": False, } ] }, @@ -2835,7 +2861,8 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": 11010101, "group": None, - "attributes": [] + "source": "manual", + "attributes": [], } ], "shapes": [ @@ -2843,6 +2870,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": None, + "source": "manual", "attributes": [ { "spec_id": 32234234, @@ -2855,16 +2883,17 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): ], "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", - "occluded": False + "occluded": False, }, { "frame": 1, "label_id": 1212121, "group": None, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 300.222, 400, 500, 1, 3], "type": "polygon", - "occluded": False + "occluded": False, }, ], "tracks": [ @@ -2872,6 +2901,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": 0, "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2897,7 +2927,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [2.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": True, - "outside": True + "outside": True, }, ] }, @@ -2905,6 +2935,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 1, "label_id": task["labels"][1]["id"], "group": None, + "source": "manual", "attributes": [], "shapes": [ { @@ -2913,7 +2944,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [1.0, 2.1, 100, 300.222], "type": "rectangle", "occluded": False, - "outside": False + "outside": False, } ] }, @@ -2940,6 +2971,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": 0, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -2965,6 +2997,19 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [2.0, 2.1, 77.2, 36.22], "type": "rectangle", "occluded": True, + "outside": False, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ] + }, + { + "frame": 2, + "points": [2.0, 2.1, 77.2, 36.22], + "type": "rectangle", + "occluded": True, "outside": True, "attributes": [ { @@ -2976,17 +3021,26 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): ] }] rectangle_tracks_wo_attrs = [{ - "frame": 1, + "frame": 0, "label_id": task["labels"][1]["id"], "group": 0, + "source": "manual", "attributes": [], "shapes": [ { - "frame": 1, + "frame": 0, "attributes": [], "points": [1.0, 2.1, 50.2, 36.6], "type": "rectangle", "occluded": False, + "outside": False, + }, + { + "frame": 1, + "attributes": [], + "points": [1.0, 2.1, 51, 36.6], + "type": "rectangle", + "occluded": False, "outside": False }, { @@ -2995,7 +3049,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "points": [1.0, 2.1, 51, 36.6], "type": "rectangle", "occluded": False, - "outside": True + "outside": True, } ] }] @@ -3004,6 +3058,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): "frame": 0, "label_id": task["labels"][0]["id"], "group": 0, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -3016,33 +3071,36 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): ], "points": [1.0, 2.1, 10.6, 53.22], "type": "rectangle", - "occluded": False + "occluded": False, }] rectangle_shapes_wo_attrs = [{ "frame": 1, "label_id": task["labels"][1]["id"], "group": 0, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 40, 50.7], "type": "rectangle", - "occluded": False + "occluded": False, }] polygon_shapes_wo_attrs = [{ "frame": 1, "label_id": task["labels"][1]["id"], "group": 0, + "source": "manual", "attributes": [], "points": [2.0, 2.1, 100, 30.22, 40, 77, 1, 3], "type": "polygon", - "occluded": False + "occluded": False, }] polygon_shapes_with_attrs = [{ "frame": 2, "label_id": task["labels"][0]["id"], "group": 1, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -3055,28 +3113,31 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): ], "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2, 4.44, 5.55], "type": "polygon", - "occluded": True + "occluded": True, }, { "frame": 2, "label_id": task["labels"][1]["id"], "group": 1, + "source": "manual", "attributes": [], "points": [4, 7, 10, 30, 4, 5.55], "type": "polygon", - "occluded": False + "occluded": False, }] tags_wo_attrs = [{ "frame": 2, "label_id": task["labels"][1]["id"], "group": 0, - "attributes": [] + "source": "manual", + "attributes": [], }] tags_with_attrs = [{ "frame": 1, "label_id": task["labels"][0]["id"], "group": 3, + "source": "manual", "attributes": [ { "spec_id": task["labels"][0]["attributes"][0]["id"], @@ -3119,6 +3180,7 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): annotations["tracks"] = rectangle_tracks_wo_attrs elif annotation_format == "MOT 1.1": + annotations["shapes"] = rectangle_shapes_wo_attrs annotations["tracks"] = rectangle_tracks_wo_attrs elif annotation_format == "LabelMe 3.0": @@ -3149,8 +3211,8 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): export_formats = data['exporters'] self.assertTrue(isinstance(import_formats, list) and import_formats) self.assertTrue(isinstance(export_formats, list) and export_formats) - import_formats = { v['name'] for v in import_formats } - export_formats = { v['name'] for v in export_formats } + import_formats = { v['name']: v for v in import_formats } + export_formats = { v['name']: v for v in export_formats } formats = { exp: exp if exp in import_formats else None for exp in export_formats } @@ -3159,12 +3221,12 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): formats['CVAT for video 1.1'] = 'CVAT 1.1' if 'CVAT for images 1.1' in export_formats: formats['CVAT for images 1.1'] = 'CVAT 1.1' - if import_formats ^ export_formats: + if set(import_formats) ^ set(export_formats): # NOTE: this may not be an error, so we should not fail print("The following import formats have no pair:", - import_formats - export_formats) + set(import_formats) - set(export_formats)) print("The following export formats have no pair:", - export_formats - import_formats) + set(export_formats) - set(import_formats)) for export_format, import_format in formats.items(): with self.subTest(export_format=export_format, @@ -3183,7 +3245,12 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): # 3. download annotation response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, "?format={}".format(export_format)) - self.assertEqual(response.status_code, HTTP_202_ACCEPTED) + if annotator and not export_formats[export_format]['enabled']: + self.assertEqual(response.status_code, + status.HTTP_405_METHOD_NOT_ALLOWED) + continue + else: + self.assertEqual(response.status_code, HTTP_202_ACCEPTED) response = self._dump_api_v1_tasks_id_annotations(task["id"], annotator, "?format={}".format(export_format)) diff --git a/cvat/apps/engine/urls.py b/cvat/apps/engine/urls.py index 6c608a00..699ea1db 100644 --- a/cvat/apps/engine/urls.py +++ b/cvat/apps/engine/urls.py @@ -9,7 +9,10 @@ from rest_framework import routers from rest_framework import permissions from drf_yasg.views import get_schema_view from drf_yasg import openapi +from django.views.generic import RedirectView +from django.conf import settings from cvat.apps.restrictions.views import RestrictionsViewSet +from cvat.apps.authentication.decorators import login_required schema_view = get_schema_view( openapi.Info( @@ -24,29 +27,44 @@ schema_view = get_schema_view( permission_classes=(permissions.IsAuthenticated,), ) +# drf-yasg component doesn't handle correctly URL_FORMAT_OVERRIDE and +# send requests with ?format=openapi suffix instead of ?scheme=openapi. +# We map the required paramater explicitly and add it into query arguments +# on the server side. +def wrap_swagger(view): + @login_required + def _map_format_to_schema(request, scheme=None): + if 'format' in request.GET: + request.GET = request.GET.copy() + format_alias = settings.REST_FRAMEWORK['URL_FORMAT_OVERRIDE'] + request.GET[format_alias] = request.GET['format'] + + return view(request, format=scheme) + + return _map_format_to_schema + router = routers.DefaultRouter(trailing_slash=False) router.register('projects', views.ProjectViewSet) router.register('tasks', views.TaskViewSet) router.register('jobs', views.JobViewSet) router.register('users', views.UserViewSet) router.register('server', views.ServerViewSet, basename='server') -router.register('plugins', views.PluginViewSet) router.register('restrictions', RestrictionsViewSet, basename='restrictions') urlpatterns = [ # Entry point for a client - path('', views.dispatch_request), - path('dashboard/', views.dispatch_request), + path('', RedirectView.as_view(url=settings.UI_URL, permanent=True, + query_string=True)), # documentation for API - path('api/swagger', views.wrap_swagger( + path('api/swagger', wrap_swagger( schema_view.without_ui(cache_timeout=0)), name='schema-json'), - path('api/swagger/', views.wrap_swagger( + path('api/swagger/', wrap_swagger( schema_view.with_ui('swagger', cache_timeout=0)), name='schema-swagger-ui'), - path('api/docs/', views.wrap_swagger( + path('api/docs/', wrap_swagger( schema_view.with_ui('redoc', cache_timeout=0)), name='schema-redoc'), # entry point for API - path('api/v1/auth/', include('cvat.apps.authentication.api_urls')), + path('api/v1/auth/', include('cvat.apps.authentication.urls')), path('api/v1/', include((router.urls, 'cvat'), namespace='v1')) ] diff --git a/cvat/apps/engine/utils.py b/cvat/apps/engine/utils.py index e3b4954d..e1ad9ef8 100644 --- a/cvat/apps/engine/utils.py +++ b/cvat/apps/engine/utils.py @@ -1,8 +1,16 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + import ast from collections import namedtuple import importlib import sys import traceback +import subprocess +import os + +from django.core.exceptions import ValidationError Import = namedtuple("Import", ["module", "name", "alias"]) @@ -58,3 +66,11 @@ def execute_python_code(source_code, global_vars=None, local_vars=None): _, _, tb = sys.exc_info() line_number = traceback.extract_tb(tb)[-1][1] raise InterpreterError("{} at line {}: {}".format(error_class, line_number, details)) + +def av_scan_paths(*paths): + if 'yes' == os.environ.get('CLAM_AV'): + command = ['clamscan', '--no-summary', '-i', '-o'] + command.extend(paths) + res = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if res.returncode: + raise ValidationError(res.stdout) diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 47dfd800..1ee9abc5 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2019 Intel Corporation +# Copyright (C) 2018-2020 Intel Corporation # # SPDX-License-Identifier: MIT @@ -13,11 +13,9 @@ import django_rq from django.conf import settings from django.contrib.auth.models import User from django.db import IntegrityError -from django.http import HttpResponse, HttpResponseNotFound -from django.shortcuts import render +from django.http import HttpResponse from django.utils import timezone from django.utils.decorators import method_decorator -from django.views.generic import RedirectView from django_filters import rest_framework as filters from django_filters.rest_framework import DjangoFilterBackend from drf_yasg import openapi @@ -34,59 +32,21 @@ from sendfile import sendfile import cvat.apps.dataset_manager as dm import cvat.apps.dataset_manager.views # pylint: disable=unused-import from cvat.apps.authentication import auth -from cvat.apps.authentication.decorators import login_required from cvat.apps.dataset_manager.serializers import DatasetFormatsSerializer from cvat.apps.engine.frame_provider import FrameProvider -from cvat.apps.engine.models import Job, Plugin, StatusChoice, Task +from cvat.apps.engine.models import Job, StatusChoice, Task from cvat.apps.engine.serializers import ( AboutSerializer, AnnotationFileSerializer, BasicUserSerializer, DataMetaSerializer, DataSerializer, ExceptionSerializer, FileInfoSerializer, JobSerializer, LabeledDataSerializer, - LogEventSerializer, PluginSerializer, ProjectSerializer, - RqStatusSerializer, TaskSerializer, UserSerializer) -from cvat.settings.base import CSS_3RDPARTY, JS_3RDPARTY + LogEventSerializer, ProjectSerializer, RqStatusSerializer, + TaskSerializer, UserSerializer) +from cvat.apps.engine.utils import av_scan_paths from . import models, task from .log import clogger, slogger -# drf-yasg component doesn't handle correctly URL_FORMAT_OVERRIDE and -# send requests with ?format=openapi suffix instead of ?scheme=openapi. -# We map the required paramater explicitly and add it into query arguments -# on the server side. -def wrap_swagger(view): - @login_required - def _map_format_to_schema(request, scheme=None): - if 'format' in request.GET: - request.GET = request.GET.copy() - format_alias = settings.REST_FRAMEWORK['URL_FORMAT_OVERRIDE'] - request.GET[format_alias] = request.GET['format'] - - return view(request, format=scheme) - - return _map_format_to_schema - -# Server REST API -@login_required -def dispatch_request(request): - """An entry point to dispatch legacy requests""" - if 'dashboard' in request.path or (request.path == '/' and 'id' not in request.GET): - return RedirectView.as_view( - url=settings.UI_URL, - permanent=True, - query_string=True - )(request) - elif request.method == 'GET' and 'id' in request.GET and request.path == '/': - return render(request, 'engine/annotation.html', { - 'css_3rdparty': CSS_3RDPARTY.get('engine', []), - 'js_3rdparty': JS_3RDPARTY.get('engine', []), - 'status_list': [str(i) for i in StatusChoice], - 'ui_url': settings.UI_URL - }) - else: - return HttpResponseNotFound() - - class ServerViewSet(viewsets.ViewSet): serializer_class = None @@ -388,7 +348,9 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): return Response(serializer.data) - @swagger_auto_schema(method='post', operation_summary='Method permanently attaches images or video to a task') + @swagger_auto_schema(method='post', operation_summary='Method permanently attaches images or video to a task', + request_body=DataSerializer, + ) @swagger_auto_schema(method='get', operation_summary='Method returns data for a specific task', manual_parameters=[ openapi.Parameter('type', in_=openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING, @@ -482,7 +444,8 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): responses={ '202': openapi.Response(description='Dump of annotations has been started'), '201': openapi.Response(description='Annotations file is ready to download'), - '200': openapi.Response(description='Download of file started') + '200': openapi.Response(description='Download of file started'), + '405': openapi.Response(description='Format is not available'), } ) @swagger_auto_schema(method='put', operation_summary='Method allows to upload task annotations', @@ -494,6 +457,7 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): responses={ '202': openapi.Response(description='Uploading has been started'), '201': openapi.Response(description='Uploading has finished'), + '405': openapi.Response(description='Format is not available'), } ) @swagger_auto_schema(method='patch', operation_summary='Method performs a partial update of annotations in a specific task', @@ -619,7 +583,8 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): ], responses={'202': openapi.Response(description='Exporting has been started'), '201': openapi.Response(description='Output file is ready for downloading'), - '200': openapi.Response(description='Download of file started') + '200': openapi.Response(description='Download of file started'), + '405': openapi.Response(description='Format is not available'), } ) @action(detail=True, methods=['GET'], serializer_class=None, @@ -753,33 +718,6 @@ class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, serializer = serializer_class(request.user, context={ "request": request }) return Response(serializer.data) -class PluginViewSet(viewsets.ModelViewSet): - queryset = Plugin.objects.all() - serializer_class = PluginSerializer - - # @action(detail=True, methods=['GET', 'PATCH', 'PUT'], serializer_class=None) - # def config(self, request, name): - # pass - - # @action(detail=True, methods=['GET', 'POST'], serializer_class=None) - # def data(self, request, name): - # pass - - # @action(detail=True, methods=['GET', 'DELETE', 'PATCH', 'PUT'], - # serializer_class=None, url_path='data/(?P\d+)') - # def data_detail(self, request, name, id): - # pass - - - @action(detail=True, methods=['GET', 'POST'], serializer_class=RqStatusSerializer) - def requests(self, request, name): - pass - - @action(detail=True, methods=['GET', 'DELETE'], - serializer_class=RqStatusSerializer, url_path='requests/(?P\d+)') - def request_detail(self, request, name, rq_id): - pass - def rq_handler(job, exc_type, exc_value, tb): job.exc_info = "".join( traceback.format_exception_only(exc_type, exc_value)) @@ -799,22 +737,27 @@ def rq_handler(job, exc_type, exc_value, tb): # tags=['tasks']) # @api_view(['PUT']) def _import_annotations(request, rq_id, rq_func, pk, format_name): + format_desc = {f.DISPLAY_NAME: f + for f in dm.views.get_import_formats()}.get(format_name) + if format_desc is None: + raise serializers.ValidationError( + "Unknown input format '{}'".format(format_name)) + elif not format_desc.ENABLED: + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) + queue = django_rq.get_queue("default") rq_job = queue.fetch_job(rq_id) if not rq_job: serializer = AnnotationFileSerializer(data=request.data) if serializer.is_valid(raise_exception=True): - if format_name not in \ - [f.DISPLAY_NAME for f in dm.views.get_import_formats()]: - raise serializers.ValidationError( - "Unknown input format '{}'".format(format_name)) - anno_file = serializer.validated_data['annotation_file'] fd, filename = mkstemp(prefix='cvat_{}'.format(pk)) with open(filename, 'wb+') as f: for chunk in anno_file.chunks(): f.write(chunk) + + av_scan_paths(filename) rq_job = queue.enqueue_call( func=rq_func, args=(pk, filename, format_name), @@ -843,9 +786,13 @@ def _export_annotations(db_task, rq_id, request, format_name, action, callback, raise serializers.ValidationError( "Unexpected action specified for the request") - if format_name not in [f.DISPLAY_NAME for f in dm.views.get_export_formats()]: + format_desc = {f.DISPLAY_NAME: f + for f in dm.views.get_export_formats()}.get(format_name) + if format_desc is None: raise serializers.ValidationError( "Unknown format specified for the request") + elif not format_desc.ENABLED: + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) queue = django_rq.get_queue("default") @@ -893,4 +840,4 @@ def _export_annotations(db_task, rq_id, request, format_name, action, callback, args=(db_task.id, format_name, server_address), job_id=rq_id, meta={ 'request_time': timezone.localtime() }, result_ttl=ttl, failure_ttl=ttl) - return Response(status=status.HTTP_202_ACCEPTED) \ No newline at end of file + return Response(status=status.HTTP_202_ACCEPTED) diff --git a/cvat/apps/git/admin.py b/cvat/apps/git/admin.py deleted file mode 100644 index b66dde17..00000000 --- a/cvat/apps/git/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/git/git.py b/cvat/apps/git/git.py index 5d0e8178..c43c9c09 100644 --- a/cvat/apps/git/git.py +++ b/cvat/apps/git/git.py @@ -78,9 +78,9 @@ class Git: # Reference on URL formats accepted by Git: # https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/url.c - host_pattern = r"((?:(?:(?:\d{1,3}\.){3}\d{1,3})|(?:[a-zA-Z0-9._-]+.[a-zA-Z]+))(?::\d+)?)" - http_pattern = r"(?:http[s]?://)?" + host_pattern + r"((?:/[a-zA-Z0-9._-]+){2})" - ssh_pattern = r"([a-zA-Z0-9._-]+)@" + host_pattern + r":([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+)" + host_pattern = r"((?:(?:(?:\d{1,3}\.){3}\d{1,3})|(?:[a-zA-Z0-9._-]+[.a-zA-Z]+))(?::\d+)?)" + http_pattern = r"(?:http[s]?://)?" + host_pattern + r"((?:/[a-zA-Z0-9._-]+){2,})" + ssh_pattern = r"([a-zA-Z0-9._-]+)@" + host_pattern + r":([a-zA-Z0-9._-]+)((?:/[a-zA-Z0-9._-]+)+)" http_match = re.match(http_pattern, self._url) ssh_match = re.match(ssh_pattern, self._url) @@ -95,7 +95,7 @@ class Git: elif ssh_match: user = ssh_match.group(1) host = ssh_match.group(2) - repos = "{}/{}".format(ssh_match.group(3), ssh_match.group(4)) + repos = "{}{}".format(ssh_match.group(3), ssh_match.group(4)) else: raise Exception("Git repository URL does not satisfy pattern") diff --git a/cvat/apps/git/tests.py b/cvat/apps/git/tests.py index 5d4d2c11..7d47c169 100644 --- a/cvat/apps/git/tests.py +++ b/cvat/apps/git/tests.py @@ -28,7 +28,7 @@ class GitUrlTest(TestCase): def test_correct_urls_can_be_parsed(self): hosts = ['host.zone', '1.2.3.4'] ports = ['', ':42'] - repo_groups = ['repo', 'r4p0'] + repo_groups = ['repo', 'r4p0', 'multi/group', 'multi/group/level'] repo_repos = ['nkjl23', 'hewj'] git_suffixes = ['', '.git'] diff --git a/cvat/apps/log_viewer/migrations/__init__.py b/cvat/apps/lambda_manager/__init__.py similarity index 100% rename from cvat/apps/log_viewer/migrations/__init__.py rename to cvat/apps/lambda_manager/__init__.py diff --git a/cvat/apps/lambda_manager/apps.py b/cvat/apps/lambda_manager/apps.py new file mode 100644 index 00000000..eda3a971 --- /dev/null +++ b/cvat/apps/lambda_manager/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class LambdaManagerConfig(AppConfig): + name = 'lambda_manager' diff --git a/cvat/apps/lambda_manager/urls.py b/cvat/apps/lambda_manager/urls.py new file mode 100644 index 00000000..0225db53 --- /dev/null +++ b/cvat/apps/lambda_manager/urls.py @@ -0,0 +1,29 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from django.urls import path +from rest_framework import routers +from django.urls import include +from . import views + +router = routers.DefaultRouter(trailing_slash=False) +# https://github.com/encode/django-rest-framework/issues/6645 +# I want to "call" my functions. To do that need to map my call method to +# POST (like get HTTP method is mapped to list(...)). One way is to implement +# own CustomRouter. But it is simpler just patch the router instance here. +router.routes[2].mapping.update({'post': 'call'}) +router.register('functions', views.FunctionViewSet, basename='function') +router.register('requests', views.RequestViewSet, basename='request') + +# GET /api/v1/lambda/functions - get list of functions +# GET /api/v1/lambda/functions/ - get information about the function +# POST /api/v1/labmda/requests - call a function +# { "function": "", "mode": "online|offline", "job": "", "frame": "", +# "points": [...], } +# GET /api/v1/lambda/requests - get list of requests +# GET /api/v1/lambda/requests/ - get status of the request +# DEL /api/v1/lambda/requests/ - cancel a request (don't delete) +urlpatterns = [ + path('api/v1/lambda/', include((router.urls, 'cvat'), namespace='v1')) +] \ No newline at end of file diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py new file mode 100644 index 00000000..d307ca51 --- /dev/null +++ b/cvat/apps/lambda_manager/views.py @@ -0,0 +1,603 @@ +import base64 +import json +from functools import wraps +from enum import Enum + +import django_rq +import requests +import rq +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist, ValidationError +from rest_framework import status, viewsets +from rest_framework.response import Response + +from cvat.apps.authentication import auth +import cvat.apps.dataset_manager as dm +from cvat.apps.engine.frame_provider import FrameProvider +from cvat.apps.engine.models import Task as TaskModel +from cvat.apps.engine.serializers import LabeledDataSerializer +from rest_framework.permissions import IsAuthenticated +from cvat.apps.engine.models import ShapeType, SourceType + +class LambdaType(Enum): + DETECTOR = "detector" + INTERACTOR = "interactor" + REID = "reid" + TRACKER = "tracker" + UNKNOWN = "unknown" + + def __str__(self): + return self.value + +class LambdaGateway: + NUCLIO_ROOT_URL = '/api/functions' + + def _http(self, method="get", scheme=None, host=None, port=None, + url=None, headers=None, data=None): + NUCLIO_GATEWAY = '{}://{}:{}'.format( + scheme or settings.NUCLIO['SCHEME'], + host or settings.NUCLIO['HOST'], + port or settings.NUCLIO['PORT']) + extra_headers = { + 'x-nuclio-project-name': 'cvat', + 'x-nuclio-function-namespace': 'nuclio', + } + if headers: + extra_headers.update(headers) + NUCLIO_TIMEOUT = settings.NUCLIO['DEFAULT_TIMEOUT'] + + if url: + url = "{}{}".format(NUCLIO_GATEWAY, url) + else: + url = NUCLIO_GATEWAY + + reply = getattr(requests, method)(url, headers=extra_headers, + timeout=NUCLIO_TIMEOUT, json=data) + reply.raise_for_status() + response = reply.json() + + return response + + def list(self): + data = self._http(url=self.NUCLIO_ROOT_URL) + response = [LambdaFunction(self, item) for item in data.values()] + return response + + def get(self, func_id): + data = self._http(url=self.NUCLIO_ROOT_URL + '/' + func_id) + response = LambdaFunction(self, data) + return response + + def invoke(self, func, payload): + # NOTE: it is overhead to invoke a function using nuclio + # dashboard REST API. Better to call host.docker.internal: + # Look at https://github.com/docker/for-linux/issues/264. + # host.docker.internal isn't supported by docker on Linux. + # There are many workarounds but let's try to use the + # simple solution. + return self._http(method="post", url='/api/function_invocations', + data=payload, headers={ + 'x-nuclio-function-name': func.id, + 'x-nuclio-path': '/' + }) + +class LambdaFunction: + def __init__(self, gateway, data): + # ID of the function (e.g. omz.public.yolo-v3) + self.id = data['metadata']['name'] + # type of the function (e.g. detector, interactor) + kind = data['metadata']['annotations'].get('type') + try: + self.kind = LambdaType(kind) + except ValueError: + self.kind = LambdaType.UNKNOWN + # dictionary of labels for the function (e.g. car, person) + spec = json.loads(data['metadata']['annotations'].get('spec') or '[]') + labels = [item['name'] for item in spec] + if len(labels) != len(set(labels)): + raise ValidationError( + "`{}` lambda function has non-unique labels".format(self.id), + code=status.HTTP_404_NOT_FOUND) + self.labels = labels + # state of the function + self.state = data['status']['state'] + # description of the function + self.description = data['spec']['description'] + # http port to access the serverless function + self.port = data["status"].get("httpPort") + # framework which is used for the function (e.g. tensorflow, openvino) + self.framework = data['metadata']['annotations'].get('framework') + # display name for the function + self.name = data['metadata']['annotations'].get('name', self.id) + self.gateway = gateway + + def to_dict(self): + response = { + 'id': self.id, + 'kind': str(self.kind), + 'labels': self.labels, + 'state': self.state, + 'description': self.description, + 'framework': self.framework, + 'name': self.name, + } + + return response + + def invoke(self, db_task, data): + try: + payload = {} + threshold = data.get("threshold") + if threshold: + payload.update({ + "threshold": threshold, + }) + quality = data.get("quality") + mapping = data.get("mapping") + if self.kind == LambdaType.DETECTOR: + payload.update({ + "image": self._get_image(db_task, data["frame"], quality) + }) + elif self.kind == LambdaType.INTERACTOR: + payload.update({ + "image": self._get_image(db_task, data["frame"], quality), + "points": data["points"], + }) + elif self.kind == LambdaType.REID: + payload.update({ + "image0": self._get_image(db_task, data["frame0"], quality), + "image1": self._get_image(db_task, data["frame1"], quality), + "boxes0": data["boxes0"], + "boxes1": data["boxes1"] + }) + max_distance = data.get("max_distance") + if max_distance: + payload.update({ + "max_distance": max_distance + }) + elif self.kind == LambdaType.TRACKER: + payload.update({ + "image": self._get_image(db_task, data["frame"], quality), + "shape": data.get("shape", None), + "state": data.get("state", None) + }) + else: + raise ValidationError( + '`{}` lambda function has incorrect type: {}' + .format(self.id, self.kind), + code=status.HTTP_500_INTERNAL_SERVER_ERROR) + except KeyError as err: + raise ValidationError( + "`{}` lambda function was called without mandatory argument: {}" + .format(self.id, str(err)), + code=status.HTTP_400_BAD_REQUEST) + + response = self.gateway.invoke(self, payload) + if self.kind == LambdaType.DETECTOR: + if mapping: + for item in response: + item["label"] = mapping.get(item["label"]) + response = [item for item in response if item["label"]] + + return response + + def _get_image(self, db_task, frame, quality): + if quality is None or quality == "original": + quality = FrameProvider.Quality.ORIGINAL + elif quality == "compressed": + quality = FrameProvider.Quality.COMPRESSED + else: + raise ValidationError( + '`{}` lambda function was run '.format(self.id) + + 'with wrong arguments (quality={})'.format(quality), + code=status.HTTP_400_BAD_REQUEST) + + frame_provider = FrameProvider(db_task.data) + image = frame_provider.get_frame(frame, quality=quality) + + return base64.b64encode(image[0].getvalue()).decode('utf-8') + + +class LambdaQueue: + def _get_queue(self): + QUEUE_NAME = "low" + return django_rq.get_queue(QUEUE_NAME) + + def get_jobs(self): + queue = self._get_queue() + # Only failed jobs are not included in the list below. + job_ids = set(queue.get_job_ids() + + queue.started_job_registry.get_job_ids() + + queue.finished_job_registry.get_job_ids() + + queue.scheduled_job_registry.get_job_ids() + + queue.deferred_job_registry.get_job_ids()) + jobs = queue.job_class.fetch_many(job_ids, queue.connection) + + return [LambdaJob(job) for job in jobs if job.meta.get("lambda")] + + def enqueue(self, lambda_func, threshold, task, quality, mapping, cleanup): + jobs = self.get_jobs() + # It is still possible to run several concurrent jobs for the same task. + # But the race isn't critical. The filtration is just a light-weight + # protection. + if list(filter(lambda job: job.get_task() == task and not job.is_finished, jobs)): + raise ValidationError( + "Only one running request is allowed for the same task #{}".format(task), + code=status.HTTP_409_CONFLICT) + + queue = self._get_queue() + # LambdaJob(None) is a workaround for python-rq. It has multiple issues + # with invocation of non-trivial functions. For example, it cannot run + # staticmethod, it cannot run a callable class. Thus I provide an object + # which has __call__ function. + job = queue.create_job(LambdaJob(None), + meta = { "lambda": True }, + kwargs = { + "function": lambda_func, + "threshold": threshold, + "task": task, + "quality": quality, + "cleanup": cleanup, + "mapping": mapping + }) + + queue.enqueue_job(job) + + return LambdaJob(job) + + def fetch_job(self, pk): + queue = self._get_queue() + job = queue.fetch_job(pk) + if job == None or not job.meta.get("lambda"): + raise ValidationError("{} lambda job is not found".format(pk), + code=status.HTTP_404_NOT_FOUND) + + return LambdaJob(job) + + +class LambdaJob: + def __init__(self, job): + self.job = job + + def to_dict(self): + lambda_func = self.job.kwargs.get("function") + return { + "id": self.job.id, + "function": { + "id": lambda_func.id if lambda_func else None, + "threshold": self.job.kwargs.get("threshold"), + "task": self.job.kwargs.get("task") + }, + "status": self.job.get_status(), + "progress": self.job.meta.get('progress', 0), + "enqueued": self.job.enqueued_at, + "started": self.job.started_at, + "ended": self.job.ended_at, + "exc_info": self.job.exc_info + } + + def get_task(self): + return self.job.kwargs.get("task") + + def get_status(self): + return self.job.get_status() + + @property + def is_finished(self): + return self.get_status() == rq.job.JobStatus.FINISHED + + @property + def is_queued(self): + return self.get_status() == rq.job.JobStatus.QUEUED + + @property + def is_failed(self): + return self.get_status() == rq.job.JobStatus.FAILED + + @property + def is_started(self): + return self.get_status() == rq.job.JobStatus.STARTED + + @property + def is_deferred(self): + return self.get_status() == rq.job.JobStatus.DEFERRED + + @property + def is_scheduled(self): + return self.get_status() == rq.job.JobStatus.SCHEDULED + + def delete(self): + self.job.delete() + + @staticmethod + def _call_detector(function, db_task, labels, quality, threshold, mapping): + class Results: + def __init__(self, task_id): + self.task_id = task_id + self.reset() + + def append_shape(self, shape): + self.data["shapes"].append(shape) + + def submit(self): + if not self.is_empty(): + serializer = LabeledDataSerializer(data=self.data) + if serializer.is_valid(raise_exception=True): + dm.task.patch_task_data(self.task_id, serializer.data, "create") + self.reset() + + def is_empty(self): + return not (self.data["tags"] or self.data["shapes"] or self.data["tracks"]) + + def reset(self): + # TODO: need to make "tags" and "tracks" are optional + # FIXME: need to provide the correct version here + self.data = {"version": 0, "tags": [], "shapes": [], "tracks": []} + + results = Results(db_task.id) + + for frame in range(db_task.data.size): + annotations = function.invoke(db_task, data={ + "frame": frame, "quality": quality, "mapping": mapping, + "threshold": threshold}) + progress = (frame + 1) / db_task.data.size + if not LambdaJob._update_progress(progress): + break + + for anno in annotations: + label_id = labels.get(anno["label"]) + if label_id is not None: + results.append_shape({ + "frame": frame, + "label_id": label_id, + "type": anno["type"], + "occluded": False, + "points": anno["points"], + "z_order": 0, + "group": None, + "attributes": [], + "source": "auto" + }) + + # Accumulate data during 100 frames before sumbitting results. + # It is optimization to make fewer calls to our server. Also + # it isn't possible to keep all results in memory. + if frame % 100 == 0: + results.submit() + + results.submit() + + @staticmethod + # progress is in [0, 1] range + def _update_progress(progress): + job = rq.get_current_job() + # If the job has been deleted, get_status will return None. Thus it will + # exist the loop. + job.meta["progress"] = int(progress * 100) + job.save_meta() + + return job.get_status() + + + @staticmethod + def _call_reid(function, db_task, quality, threshold, max_distance): + data = dm.task.get_task_data(db_task.id) + boxes_by_frame = [[] for _ in range(db_task.data.size)] + shapes_without_boxes = [] + for shape in data["shapes"]: + if shape["type"] == str(ShapeType.RECTANGLE): + boxes_by_frame[shape["frame"]].append(shape) + else: + shapes_without_boxes.append(shape) + + paths = {} + for frame in range(db_task.data.size - 1): + boxes0 = boxes_by_frame[frame] + for box in boxes0: + if "path_id" not in box: + path_id = len(paths) + paths[path_id] = [box] + box["path_id"] = path_id + + boxes1 = boxes_by_frame[frame + 1] + if boxes0 and boxes1: + matching = function.invoke(db_task, data={ + "frame0": frame, "frame1": frame + 1, "quality": quality, + "boxes0": boxes0, "boxes1": boxes1, "threshold": threshold, + "max_distance": max_distance}) + + for idx0, idx1 in enumerate(matching): + if idx1 >= 0: + path_id = boxes0[idx0]["path_id"] + boxes1[idx1]["path_id"] = path_id + paths[path_id].append(boxes1[idx1]) + + progress = (frame + 2) / db_task.data.size + if not LambdaJob._update_progress(progress): + break + + + for box in boxes_by_frame[db_task.data.size - 1]: + if "path_id" not in box: + path_id = len(paths) + paths[path_id] = [box] + box["path_id"] = path_id + + tracks = [] + for path_id in paths: + box0 = paths[path_id][0] + tracks.append({ + "label_id": box0["label_id"], + "group": None, + "attributes": [], + "frame": box0["frame"], + "shapes": paths[path_id], + "source": str(SourceType.AUTO) + }) + + for box in tracks[-1]["shapes"]: + box.pop("id", None) + box.pop("path_id") + box.pop("group") + box.pop("label_id") + box.pop("source") + box["outside"] = False + box["attributes"] = [] + + for track in tracks: + if track["shapes"][-1]["frame"] != db_task.data.size - 1: + box = track["shapes"][-1].copy() + box["outside"] = True + box["frame"] += 1 + track["shapes"].append(box) + + if tracks: + data["shapes"] = shapes_without_boxes + data["tracks"].extend(tracks) + + serializer = LabeledDataSerializer(data=data) + if serializer.is_valid(raise_exception=True): + dm.task.put_task_data(db_task.id, serializer.data) + + @staticmethod + def __call__(function, task, quality, cleanup, **kwargs): + # TODO: need logging + db_task = TaskModel.objects.get(pk=task) + if cleanup: + dm.task.delete_task_data(db_task.id) + db_labels = db_task.label_set.prefetch_related("attributespec_set").all() + labels = {db_label.name:db_label.id for db_label in db_labels} + + if function.kind == LambdaType.DETECTOR: + LambdaJob._call_detector(function, db_task, labels, quality, + kwargs.get("threshold"), kwargs.get("mapping")) + elif function.kind == LambdaType.REID: + LambdaJob._call_reid(function, db_task, quality, + kwargs.get("threshold"), kwargs.get("max_distance")) + +def return_response(success_code=status.HTTP_200_OK): + def wrap_response(func): + @wraps(func) + def func_wrapper(*args, **kwargs): + data = None + status_code = success_code + try: + data = func(*args, **kwargs) + except requests.ConnectionError as err: + status_code = status.HTTP_503_SERVICE_UNAVAILABLE + data = str(err) + except requests.HTTPError as err: + status_code = err.response.status_code + data = str(err) + except requests.Timeout as err: + status_code = status.HTTP_504_GATEWAY_TIMEOUT + data = str(err) + except requests.RequestException as err: + status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + data = str(err) + except ValidationError as err: + status_code = err.code + data = err.message + + return Response(data=data, status=status_code) + + return func_wrapper + return wrap_response + +class FunctionViewSet(viewsets.ViewSet): + lookup_value_regex = '[a-zA-Z0-9_.-]+' + lookup_field = 'func_id' + + def get_permissions(self): + http_method = self.request.method + permissions = [IsAuthenticated] + + if http_method in ["POST"]: + permissions.append(auth.TaskAccessPermission) + + return [perm() for perm in permissions] + + @return_response() + def list(self, request): + gateway = LambdaGateway() + return [f.to_dict() for f in gateway.list()] + + @return_response() + def retrieve(self, request, func_id): + gateway = LambdaGateway() + return gateway.get(func_id).to_dict() + + @return_response() + def call(self, request, func_id): + try: + task_id = request.data['task'] + db_task = TaskModel.objects.get(pk=task_id) + # Check that the user has enough permissions to read + # data from the task. + self.check_object_permissions(self.request, db_task) + except (KeyError, ObjectDoesNotExist) as err: + raise ValidationError( + '`{}` lambda function was run '.format(func_id) + + 'with wrong arguments ({})'.format(str(err)), + code=status.HTTP_400_BAD_REQUEST) + + gateway = LambdaGateway() + lambda_func = gateway.get(func_id) + + return lambda_func.invoke(db_task, request.data) + +class RequestViewSet(viewsets.ViewSet): + def get_permissions(self): + http_method = self.request.method + permissions = [IsAuthenticated] + + if http_method in ["POST", "DELETE"]: + permissions.append(auth.TaskChangePermission) + + return [perm() for perm in permissions] + + @return_response() + def list(self, request): + queue = LambdaQueue() + return [job.to_dict() for job in queue.get_jobs()] + + @return_response() + def create(self, request): + try: + function = request.data['function'] + threshold = request.data.get('threshold') + task = request.data['task'] + quality = request.data.get("quality") + cleanup = request.data.get('cleanup', False) + mapping = request.data.get('mapping') + + db_task = TaskModel.objects.get(pk=task) + # Check that the user has enough permissions to modify + # the task. + self.check_object_permissions(self.request, db_task) + except (KeyError, ObjectDoesNotExist) as err: + raise ValidationError( + '`{}` lambda function was run '.format(function) + + 'with wrong arguments ({})'.format(str(err)), + code=status.HTTP_400_BAD_REQUEST) + + gateway = LambdaGateway() + queue = LambdaQueue() + lambda_func = gateway.get(function) + job = queue.enqueue(lambda_func, threshold, task, quality, + mapping, cleanup) + + return job.to_dict() + + @return_response() + def retrieve(self, request, pk): + queue = LambdaQueue() + job = queue.fetch_job(pk) + + return job.to_dict() + + @return_response(status.HTTP_204_NO_CONTENT) + def delete(self, request, pk): + queue = LambdaQueue() + job = queue.fetch_job(pk) + job.delete() diff --git a/cvat/apps/reid/README.md b/cvat/apps/reid/README.md deleted file mode 100644 index 8cf29c50..00000000 --- a/cvat/apps/reid/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Re-Identification Application - -## About the application - -The ReID application uses deep learning model to perform an automatic bbox merging between neighbor frames. -You can use "Merge" and "Split" functionality to edit automatically generated annotation. - -## Installation - -This application will be installed automatically with the [OpenVINO](https://github.com/opencv/cvat/blob/develop/components/openvino/README.md) component. - -## Running - -For starting the ReID merge process: - -- Open an annotation job -- Open the menu -- Click the "Run ReID Merge" button -- Click the "Submit" button. Also here you can experiment with values of model threshold or maximum distance. - - Model threshold is maximum cosine distance between objects embeddings. - - Maximum distance defines a maximum radius that an object can diverge between neightbor frames. -- The process will be run. You can cancel it in the menu. diff --git a/cvat/apps/reid/__init__.py b/cvat/apps/reid/__init__.py deleted file mode 100644 index 4a9759cc..00000000 --- a/cvat/apps/reid/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -default_app_config = 'cvat.apps.reid.apps.ReidConfig' - -JS_3RDPARTY['engine'] = JS_3RDPARTY.get('engine', []) + ['reid/js/enginePlugin.js'] diff --git a/cvat/apps/reid/apps.py b/cvat/apps/reid/apps.py deleted file mode 100644 index f6aa66e1..00000000 --- a/cvat/apps/reid/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - -class ReidConfig(AppConfig): - name = 'cvat.apps.reid' diff --git a/cvat/apps/reid/reid.py b/cvat/apps/reid/reid.py deleted file mode 100644 index bf79bac9..00000000 --- a/cvat/apps/reid/reid.py +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import os -import rq -import cv2 -import math -import numpy -import itertools - -from openvino.inference_engine import IENetwork, IEPlugin -from scipy.optimize import linear_sum_assignment -from scipy.spatial.distance import euclidean, cosine - -from cvat.apps.engine.models import Job -from cvat.apps.engine.frame_provider import FrameProvider - - -class ReID: - __threshold = None - __max_distance = None - __frame_urls = None - __frame_boxes = None - __stop_frame = None - __plugin = None - __executable_network = None - __input_blob_name = None - __output_blob_name = None - __input_height = None - __input_width = None - - - def __init__(self, jid, data): - self.__threshold = data["threshold"] - self.__max_distance = data["maxDistance"] - - self.__frame_boxes = {} - - db_job = Job.objects.select_related('segment__task').get(pk = jid) - db_segment = db_job.segment - db_task = db_segment.task - self.__frame_iter = itertools.islice( - FrameProvider(db_task.data).get_frames(FrameProvider.Quality.ORIGINAL), - db_segment.start_frame, - db_segment.stop_frame + 1, - ) - - self.__stop_frame = db_segment.stop_frame - for frame in range(db_segment.start_frame, db_segment.stop_frame + 1): - self.__frame_boxes[frame] = [box for box in data["boxes"] if box["frame"] == frame] - - IE_PLUGINS_PATH = os.getenv('IE_PLUGINS_PATH', None) - REID_MODEL_DIR = os.getenv('REID_MODEL_DIR', None) - - if not IE_PLUGINS_PATH: - raise Exception("Environment variable 'IE_PLUGINS_PATH' isn't defined") - if not REID_MODEL_DIR: - raise Exception("Environment variable 'REID_MODEL_DIR' isn't defined") - - REID_XML = os.path.join(REID_MODEL_DIR, "reid.xml") - REID_BIN = os.path.join(REID_MODEL_DIR, "reid.bin") - - self.__plugin = IEPlugin(device="CPU", plugin_dirs=[IE_PLUGINS_PATH]) - network = IENetwork.from_ir(model=REID_XML, weights=REID_BIN) - self.__input_blob_name = next(iter(network.inputs)) - self.__output_blob_name = next(iter(network.outputs)) - self.__input_height, self.__input_width = network.inputs[self.__input_blob_name].shape[-2:] - self.__executable_network = self.__plugin.load(network=network) - del network - - - def __del__(self): - if self.__executable_network: - del self.__executable_network - self.__executable_network = None - - if self.__plugin: - del self.__plugin - self.__plugin = None - - - def __boxes_are_compatible(self, cur_box, next_box): - cur_c_x = (cur_box["points"][0] + cur_box["points"][2]) / 2 - cur_c_y = (cur_box["points"][1] + cur_box["points"][3]) / 2 - next_c_x = (next_box["points"][0] + next_box["points"][2]) / 2 - next_c_y = (next_box["points"][1] + next_box["points"][3]) / 2 - compatible_distance = euclidean([cur_c_x, cur_c_y], [next_c_x, next_c_y]) <= self.__max_distance - compatible_label = cur_box["label_id"] == next_box["label_id"] - return compatible_distance and compatible_label and "path_id" not in next_box - - - def __compute_difference(self, image_1, image_2): - image_1 = cv2.resize(image_1, (self.__input_width, self.__input_height)).transpose((2,0,1)) - image_2 = cv2.resize(image_2, (self.__input_width, self.__input_height)).transpose((2,0,1)) - - input_1 = { - self.__input_blob_name: image_1[numpy.newaxis, ...] - } - - input_2 = { - self.__input_blob_name: image_2[numpy.newaxis, ...] - } - - embedding_1 = self.__executable_network.infer(inputs = input_1)[self.__output_blob_name] - embedding_2 = self.__executable_network.infer(inputs = input_2)[self.__output_blob_name] - - embedding_1 = embedding_1.reshape(embedding_1.size) - embedding_2 = embedding_2.reshape(embedding_2.size) - - return cosine(embedding_1, embedding_2) - - - def __compute_difference_matrix(self, cur_boxes, next_boxes, cur_image, next_image): - def _int(number, upper): - return math.floor(numpy.clip(number, 0, upper - 1)) - - default_mat_value = 1000.0 - - matrix = numpy.full([len(cur_boxes), len(next_boxes)], default_mat_value, dtype=float) - for row, cur_box in enumerate(cur_boxes): - cur_width = cur_image.shape[1] - cur_height = cur_image.shape[0] - cur_xtl, cur_xbr, cur_ytl, cur_ybr = ( - _int(cur_box["points"][0], cur_width), _int(cur_box["points"][2], cur_width), - _int(cur_box["points"][1], cur_height), _int(cur_box["points"][3], cur_height) - ) - - for col, next_box in enumerate(next_boxes): - next_box = next_boxes[col] - next_width = next_image.shape[1] - next_height = next_image.shape[0] - next_xtl, next_xbr, next_ytl, next_ybr = ( - _int(next_box["points"][0], next_width), _int(next_box["points"][2], next_width), - _int(next_box["points"][1], next_height), _int(next_box["points"][3], next_height) - ) - - if not self.__boxes_are_compatible(cur_box, next_box): - continue - - crop_1 = cur_image[cur_ytl:cur_ybr, cur_xtl:cur_xbr] - crop_2 = next_image[next_ytl:next_ybr, next_xtl:next_xbr] - matrix[row][col] = self.__compute_difference(crop_1, crop_2) - - return matrix - - - def __apply_matching(self): - frames = sorted(list(self.__frame_boxes.keys())) - job = rq.get_current_job() - box_tracks = {} - - next_image = cv2.imdecode(numpy.fromstring((next(self.__frame_iter)[0]).read(), numpy.uint8), cv2.IMREAD_COLOR) - for idx, (cur_frame, next_frame) in enumerate(list(zip(frames[:-1], frames[1:]))): - job.refresh() - if "cancel" in job.meta: - return None - - job.meta["progress"] = idx * 100.0 / len(frames) - job.save_meta() - - cur_boxes = self.__frame_boxes[cur_frame] - next_boxes = self.__frame_boxes[next_frame] - - for box in cur_boxes: - if "path_id" not in box: - path_id = len(box_tracks) - box_tracks[path_id] = [box] - box["path_id"] = path_id - - if not (len(cur_boxes) and len(next_boxes)): - continue - - cur_image = next_image - next_image = cv2.imdecode(numpy.fromstring((next(self.__frame_iter)[0]).read(), numpy.uint8), cv2.IMREAD_COLOR) - difference_matrix = self.__compute_difference_matrix(cur_boxes, next_boxes, cur_image, next_image) - cur_idxs, next_idxs = linear_sum_assignment(difference_matrix) - for idx, cur_idx in enumerate(cur_idxs): - if (difference_matrix[cur_idx][next_idxs[idx]]) <= self.__threshold: - cur_box = cur_boxes[cur_idx] - next_box = next_boxes[next_idxs[idx]] - next_box["path_id"] = cur_box["path_id"] - box_tracks[cur_box["path_id"]].append(next_box) - - for box in self.__frame_boxes[frames[-1]]: - if "path_id" not in box: - path_id = len(box_tracks) - box["path_id"] = path_id - box_tracks[path_id] = [box] - - return box_tracks - - - def run(self): - box_tracks = self.__apply_matching() - output = [] - - # ReID process has been canceled - if box_tracks is None: - return - - for path_id in box_tracks: - output.append({ - "label_id": box_tracks[path_id][0]["label_id"], - "group": None, - "attributes": [], - "frame": box_tracks[path_id][0]["frame"], - "shapes": box_tracks[path_id] - }) - - for box in output[-1]["shapes"]: - if "id" in box: - del box["id"] - del box["path_id"] - del box["group"] - del box["label_id"] - box["outside"] = False - box["attributes"] = [] - - for path in output: - if path["shapes"][-1]["frame"] != self.__stop_frame: - copy = path["shapes"][-1].copy() - copy["outside"] = True - copy["frame"] += 1 - path["shapes"].append(copy) - - return output diff --git a/cvat/apps/reid/static/reid/js/enginePlugin.js b/cvat/apps/reid/static/reid/js/enginePlugin.js deleted file mode 100644 index 28fabb82..00000000 --- a/cvat/apps/reid/static/reid/js/enginePlugin.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global showMessage userConfirm */ - - -document.addEventListener('DOMContentLoaded', () => { - async function run(overlay, cancelButton, thresholdInput, distanceInput) { - const collection = window.cvat.data.get(); - const data = { - threshold: +thresholdInput.prop('value'), - maxDistance: +distanceInput.prop('value'), - boxes: collection.shapes.filter(el => el.type === 'rectangle'), - }; - - overlay.removeClass('hidden'); - cancelButton.prop('disabled', true); - - async function checkCallback() { - let jobData = null; - try { - jobData = await $.get(`/reid/check/${window.cvat.job.id}`); - } catch (errorData) { - overlay.addClass('hidden'); - const message = `Can not check ReID merge. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - } - - if (jobData.progress) { - cancelButton.text(`Cancel ReID Merge (${jobData.progress.toString().slice(0, 4)}%)`); - } - - if (['queued', 'started'].includes(jobData.status)) { - setTimeout(checkCallback, 1000); - } else { - overlay.addClass('hidden'); - - if (jobData.status === 'finished') { - if (jobData.result) { - const result = JSON.parse(jobData.result); - collection.shapes = collection.shapes - .filter(el => el.type !== 'rectangle'); - collection.tracks = collection.tracks - .concat(result); - - window.cvat.data.clear(); - window.cvat.data.set(collection); - - showMessage('ReID merge has done.'); - } else { - showMessage('ReID merge been canceled.'); - } - } else if (jobData.status === 'failed') { - const message = `ReID merge has fallen. Error: '${jobData.stderr}'`; - showMessage(message); - } else { - let message = `Check request returned "${jobData.status}" status.`; - if (jobData.stderr) { - message += ` Error: ${jobData.stderr}`; - } - showMessage(message); - } - } - } - - try { - await $.ajax({ - url: `/reid/start/job/${window.cvat.job.id}`, - type: 'POST', - data: JSON.stringify(data), - contentType: 'application/json', - }); - - setTimeout(checkCallback, 1000); - } catch (errorData) { - overlay.addClass('hidden'); - const message = `Can not start ReID merge. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - } finally { - cancelButton.prop('disabled', false); - } - } - - async function cancel(overlay, cancelButton) { - cancelButton.prop('disabled', true); - try { - await $.get(`/reid/cancel/${window.cvat.job.id}`); - overlay.addClass('hidden'); - cancelButton.text('Cancel ReID Merge (0%)'); - } catch (errorData) { - const message = `Can not cancel ReID process. Code: ${errorData.status}. Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - } finally { - cancelButton.prop('disabled', false); - } - } - - const buttonsUI = $('#engineMenuButtons'); - const reidWindowId = 'reidSubmitWindow'; - const reidThresholdValueId = 'reidThresholdValue'; - const reidDistanceValueId = 'reidDistanceValue'; - const reidCancelMergeId = 'reidCancelMerge'; - const reidSubmitMergeId = 'reidSubmitMerge'; - const reidCancelButtonId = 'reidCancelReID'; - const reidOverlay = 'reidOverlay'; - - $('').on('click', () => { - $('#annotationMenu').addClass('hidden'); - $(`#${reidWindowId}`).removeClass('hidden'); - }).addClass('menuButton semiBold h2').prependTo(buttonsUI); - - $(` - - `).appendTo('body'); - - $(` - - `).appendTo('body'); - - $(`#${reidCancelMergeId}`).on('click', () => { - $(`#${reidWindowId}`).addClass('hidden'); - }); - - $(`#${reidCancelButtonId}`).on('click', () => { - userConfirm('ReID process will be canceld. Are you sure?', () => { - cancel($(`#${reidOverlay}`), $(`#${reidCancelButtonId}`)); - }); - }); - - $(`#${reidSubmitMergeId}`).on('click', () => { - $(`#${reidWindowId}`).addClass('hidden'); - run($(`#${reidOverlay}`), $(`#${reidCancelButtonId}`), - $(`#${reidThresholdValueId}`), $(`#${reidDistanceValueId}`)) - .catch((error) => { - setTimeout(() => { - throw error; - }); - }); - }); -}); diff --git a/cvat/apps/reid/urls.py b/cvat/apps/reid/urls.py deleted file mode 100644 index 431b1119..00000000 --- a/cvat/apps/reid/urls.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('start/job/', views.start), - path('cancel/', views.cancel), - path('check/', views.check), - path('enabled', views.enabled), -] diff --git a/cvat/apps/reid/views.py b/cvat/apps/reid/views.py deleted file mode 100644 index 100151ee..00000000 --- a/cvat/apps/reid/views.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse -from cvat.apps.authentication.decorators import login_required -from rules.contrib.views import permission_required, objectgetter - -from cvat.apps.engine.models import Job -from cvat.apps.reid.reid import ReID - -import django_rq -import json -import rq - - -def _create_thread(jid, data): - job = rq.get_current_job() - reid_obj = ReID(jid, data) - job.meta["result"] = json.dumps(reid_obj.run()) - job.save_meta() - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, 'jid'), raise_exception=True) -def start(request, jid): - try: - data = json.loads(request.body.decode('utf-8')) - queue = django_rq.get_queue("low") - job_id = "reid.create.{}".format(jid) - job = queue.fetch_job(job_id) - if job is not None and (job.is_started or job.is_queued): - raise Exception('ReID process has been already started') - queue.enqueue_call(func=_create_thread, args=(jid, data), job_id=job_id, timeout=7200) - job = queue.fetch_job(job_id) - job.meta = {} - job.save_meta() - except Exception as e: - return HttpResponseBadRequest(str(e)) - - return HttpResponse() - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, 'jid'), raise_exception=True) -def check(request, jid): - try: - queue = django_rq.get_queue("low") - rq_id = "reid.create.{}".format(jid) - job = queue.fetch_job(rq_id) - if job is not None and "cancel" in job.meta: - return JsonResponse({"status": "finished"}) - data = {} - if job is None: - data["status"] = "unknown" - elif job.is_queued: - data["status"] = "queued" - elif job.is_started: - data["status"] = "started" - if "progress" in job.meta: - data["progress"] = job.meta["progress"] - elif job.is_finished: - data["status"] = "finished" - data["result"] = job.meta["result"] - job.delete() - else: - data["status"] = "failed" - data["stderr"] = job.exc_info - job.delete() - - except Exception as ex: - data["stderr"] = str(ex) - data["status"] = "unknown" - - return JsonResponse(data) - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, 'jid'), raise_exception=True) -def cancel(request, jid): - try: - queue = django_rq.get_queue("low") - rq_id = "reid.create.{}".format(jid) - job = queue.fetch_job(rq_id) - if job is None or job.is_finished or job.is_failed: - raise Exception("Task is not being annotated currently") - elif "cancel" not in job.meta: - job.meta["cancel"] = True - job.save_meta() - except Exception as e: - return HttpResponseBadRequest(str(e)) - - return HttpResponse() - -def enabled(request): - return HttpResponse() diff --git a/cvat/apps/restrictions/migrations/__init__.py b/cvat/apps/restrictions/migrations/__init__.py deleted file mode 100644 index f19d9bef..00000000 --- a/cvat/apps/restrictions/migrations/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright (C) 2020 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/tf_annotation/__init__.py b/cvat/apps/tf_annotation/__init__.py deleted file mode 100644 index a0fca4cb..00000000 --- a/cvat/apps/tf_annotation/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/tf_annotation/admin.py b/cvat/apps/tf_annotation/admin.py deleted file mode 100644 index af8dfc47..00000000 --- a/cvat/apps/tf_annotation/admin.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.contrib import admin - -# Register your models here. - diff --git a/cvat/apps/tf_annotation/apps.py b/cvat/apps/tf_annotation/apps.py deleted file mode 100644 index d07a37b1..00000000 --- a/cvat/apps/tf_annotation/apps.py +++ /dev/null @@ -1,11 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - - -class TFAnnotationConfig(AppConfig): - name = 'tf_annotation' - diff --git a/cvat/apps/tf_annotation/migrations/__init__.py b/cvat/apps/tf_annotation/migrations/__init__.py deleted file mode 100644 index d8e62e54..00000000 --- a/cvat/apps/tf_annotation/migrations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - diff --git a/cvat/apps/tf_annotation/models.py b/cvat/apps/tf_annotation/models.py deleted file mode 100644 index cdf3b082..00000000 --- a/cvat/apps/tf_annotation/models.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.db import models - -# Create your models here. - diff --git a/cvat/apps/tf_annotation/tests.py b/cvat/apps/tf_annotation/tests.py deleted file mode 100644 index 53bc3b7a..00000000 --- a/cvat/apps/tf_annotation/tests.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.test import TestCase - -# Create your tests here. - diff --git a/cvat/apps/tf_annotation/urls.py b/cvat/apps/tf_annotation/urls.py deleted file mode 100644 index f84019be..00000000 --- a/cvat/apps/tf_annotation/urls.py +++ /dev/null @@ -1,14 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('create/task/', views.create), - path('check/task/', views.check), - path('cancel/task/', views.cancel), - path('meta/get', views.get_meta_info), -] diff --git a/cvat/apps/tf_annotation/views.py b/cvat/apps/tf_annotation/views.py deleted file mode 100644 index 1c321f14..00000000 --- a/cvat/apps/tf_annotation/views.py +++ /dev/null @@ -1,346 +0,0 @@ - -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from rest_framework.decorators import api_view -from rules.contrib.views import permission_required, objectgetter -from cvat.apps.authentication.decorators import login_required -from cvat.apps.dataset_manager.task import put_task_data -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.engine.serializers import LabeledDataSerializer -from cvat.apps.engine.frame_provider import FrameProvider - -import django_rq -import os -import rq - -import tensorflow as tf -import numpy as np - -from PIL import Image -from cvat.apps.engine.log import slogger - - -def load_image_into_numpy(image): - (im_width, im_height) = image.size - return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8) - - -def run_inference_engine_annotation(image_list, labels_mapping, treshold): - from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network - - def _normalize_box(box, w, h, dw, dh): - xmin = min(int(box[0] * dw * w), w) - ymin = min(int(box[1] * dh * h), h) - xmax = min(int(box[2] * dw * w), w) - ymax = min(int(box[3] * dh * h), h) - return xmin, ymin, xmax, ymax - - result = {} - MODEL_PATH = os.environ.get('TF_ANNOTATION_MODEL_PATH') - if MODEL_PATH is None: - raise OSError('Model path env not found in the system.') - - core_or_plugin = make_plugin_or_core() - network = make_network('{}.xml'.format(MODEL_PATH), '{}.bin'.format(MODEL_PATH)) - input_blob_name = next(iter(network.inputs)) - output_blob_name = next(iter(network.outputs)) - if getattr(core_or_plugin, 'load_network', False): - executable_network = core_or_plugin.load_network(network, 'CPU') - else: - executable_network = core_or_plugin.load(network=network) - job = rq.get_current_job() - - del network - - try: - for image_num, im_name in enumerate(image_list): - - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(image_list) - job.save_meta() - - image = Image.open(im_name) - width, height = image.size - image.thumbnail((600, 600), Image.ANTIALIAS) - dwidth, dheight = 600 / image.size[0], 600 / image.size[1] - image = image.crop((0, 0, 600, 600)) - image_np = load_image_into_numpy(image) - image_np = np.transpose(image_np, (2, 0, 1)) - prediction = executable_network.infer(inputs={input_blob_name: image_np[np.newaxis, ...]})[output_blob_name][0][0] - for obj in prediction: - obj_class = int(obj[1]) - obj_value = obj[2] - if obj_class and obj_class in labels_mapping and obj_value >= treshold: - label = labels_mapping[obj_class] - if label not in result: - result[label] = [] - xmin, ymin, xmax, ymax = _normalize_box(obj[3:7], width, height, dwidth, dheight) - result[label].append([image_num, xmin, ymin, xmax, ymax]) - finally: - del executable_network - del plugin - - return result - - -def run_tensorflow_annotation(frame_provider, labels_mapping, treshold): - def _normalize_box(box, w, h): - xmin = int(box[1] * w) - ymin = int(box[0] * h) - xmax = int(box[3] * w) - ymax = int(box[2] * h) - return xmin, ymin, xmax, ymax - - result = {} - model_path = os.environ.get('TF_ANNOTATION_MODEL_PATH') - if model_path is None: - raise OSError('Model path env not found in the system.') - job = rq.get_current_job() - - detection_graph = tf.Graph() - with detection_graph.as_default(): - od_graph_def = tf.GraphDef() - with tf.gfile.GFile(model_path + '.pb', 'rb') as fid: - serialized_graph = fid.read() - od_graph_def.ParseFromString(serialized_graph) - tf.import_graph_def(od_graph_def, name='') - - try: - config = tf.ConfigProto() - config.gpu_options.allow_growth=True - sess = tf.Session(graph=detection_graph, config=config) - frames = frame_provider.get_frames(frame_provider.Quality.ORIGINAL) - for image_num, (image, _) in enumerate(frames): - - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(frame_provider) - job.save_meta() - - image = Image.open(image) - width, height = image.size - if width > 1920 or height > 1080: - image = image.resize((width // 2, height // 2), Image.ANTIALIAS) - image_np = load_image_into_numpy(image) - image_np_expanded = np.expand_dims(image_np, axis=0) - - image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') - boxes = detection_graph.get_tensor_by_name('detection_boxes:0') - scores = detection_graph.get_tensor_by_name('detection_scores:0') - classes = detection_graph.get_tensor_by_name('detection_classes:0') - num_detections = detection_graph.get_tensor_by_name('num_detections:0') - (boxes, scores, classes, num_detections) = sess.run([boxes, scores, classes, num_detections], feed_dict={image_tensor: image_np_expanded}) - - for i in range(len(classes[0])): - if classes[0][i] in labels_mapping.keys(): - if scores[0][i] >= treshold: - xmin, ymin, xmax, ymax = _normalize_box(boxes[0][i], width, height) - label = labels_mapping[classes[0][i]] - if label not in result: - result[label] = [] - result[label].append([image_num, xmin, ymin, xmax, ymax]) - finally: - sess.close() - del sess - return result - -def convert_to_cvat_format(data): - result = { - "tracks": [], - "shapes": [], - "tags": [], - "version": 0, - } - - for label in data: - boxes = data[label] - for box in boxes: - result['shapes'].append({ - "type": "rectangle", - "label_id": label, - "frame": box[0], - "points": [box[1], box[2], box[3], box[4]], - "z_order": 0, - "group": None, - "occluded": False, - "attributes": [], - }) - - return result - -def create_thread(tid, labels_mapping, user): - try: - TRESHOLD = 0.5 - # Init rq job - job = rq.get_current_job() - job.meta['progress'] = 0 - job.save_meta() - # Get job indexes and segment length - db_task = TaskModel.objects.get(pk=tid) - # Get image list - image_list = FrameProvider(db_task.data) - - # Run auto annotation by tf - result = None - slogger.glob.info("tf annotation with tensorflow framework for task {}".format(tid)) - result = run_tensorflow_annotation(image_list, labels_mapping, TRESHOLD) - - if result is None: - slogger.glob.info('tf annotation for task {} canceled by user'.format(tid)) - return - - # Modify data format and save - result = convert_to_cvat_format(result) - serializer = LabeledDataSerializer(data = result) - if serializer.is_valid(raise_exception=True): - put_task_data(tid, result) - slogger.glob.info('tf annotation for task {} done'.format(tid)) - except Exception as ex: - try: - slogger.task[tid].exception('exception was occured during tf annotation of the task', exc_info=True) - except: - slogger.glob.exception('exception was occured during tf annotation of the task {}'.format(tid), exc_info=True) - raise ex - -@api_view(['POST']) -@login_required -def get_meta_info(request): - try: - queue = django_rq.get_queue('low') - tids = request.data - result = {} - for tid in tids: - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None: - result[tid] = { - "active": job.is_queued or job.is_started, - "success": not job.is_failed - } - - return JsonResponse(result) - except Exception as ex: - slogger.glob.exception('exception was occured during tf meta request', exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def create(request, tid): - slogger.glob.info('tf annotation create request for task {}'.format(tid)) - try: - db_task = TaskModel.objects.get(pk=tid) - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None and (job.is_started or job.is_queued): - raise Exception("The process is already running") - - db_labels = db_task.label_set.prefetch_related('attributespec_set').all() - db_labels = {db_label.id:db_label.name for db_label in db_labels} - - tf_annotation_labels = { - "person": 1, "bicycle": 2, "car": 3, "motorcycle": 4, "airplane": 5, - "bus": 6, "train": 7, "truck": 8, "boat": 9, "traffic_light": 10, - "fire_hydrant": 11, "stop_sign": 13, "parking_meter": 14, "bench": 15, - "bird": 16, "cat": 17, "dog": 18, "horse": 19, "sheep": 20, "cow": 21, - "elephant": 22, "bear": 23, "zebra": 24, "giraffe": 25, "backpack": 27, - "umbrella": 28, "handbag": 31, "tie": 32, "suitcase": 33, "frisbee": 34, - "skis": 35, "snowboard": 36, "sports_ball": 37, "kite": 38, "baseball_bat": 39, - "baseball_glove": 40, "skateboard": 41, "surfboard": 42, "tennis_racket": 43, - "bottle": 44, "wine_glass": 46, "cup": 47, "fork": 48, "knife": 49, "spoon": 50, - "bowl": 51, "banana": 52, "apple": 53, "sandwich": 54, "orange": 55, "broccoli": 56, - "carrot": 57, "hot_dog": 58, "pizza": 59, "donut": 60, "cake": 61, "chair": 62, - "couch": 63, "potted_plant": 64, "bed": 65, "dining_table": 67, "toilet": 70, - "tv": 72, "laptop": 73, "mouse": 74, "remote": 75, "keyboard": 76, "cell_phone": 77, - "microwave": 78, "oven": 79, "toaster": 80, "sink": 81, "refrigerator": 83, - "book": 84, "clock": 85, "vase": 86, "scissors": 87, "teddy_bear": 88, "hair_drier": 89, - "toothbrush": 90 - } - - labels_mapping = {} - for key, labels in db_labels.items(): - if labels in tf_annotation_labels.keys(): - labels_mapping[tf_annotation_labels[labels]] = key - - if not len(labels_mapping.values()): - raise Exception('No labels found for tf annotation') - - # Run tf annotation job - queue.enqueue_call(func=create_thread, - args=(tid, labels_mapping, request.user), - job_id='tf_annotation.create/{}'.format(tid), - timeout=604800) # 7 days - - slogger.task[tid].info('tensorflow annotation job enqueued with labels {}'.format(labels_mapping)) - - except Exception as ex: - try: - slogger.task[tid].exception("exception was occured during tensorflow annotation request", exc_info=True) - except: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() - -@login_required -@permission_required(perm=['engine.task.access'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def check(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None and 'cancel' in job.meta: - return JsonResponse({'status': 'finished'}) - data = {} - if job is None: - data['status'] = 'unknown' - elif job.is_queued: - data['status'] = 'queued' - elif job.is_started: - data['status'] = 'started' - data['progress'] = job.meta['progress'] - elif job.is_finished: - data['status'] = 'finished' - job.delete() - else: - data['status'] = 'failed' - data['stderr'] = job.exc_info - job.delete() - - except Exception: - data['status'] = 'unknown' - - return JsonResponse(data) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def cancel(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is None or job.is_finished or job.is_failed: - raise Exception('Task is not being annotated currently') - elif 'cancel' not in job.meta: - job.meta['cancel'] = True - job.save() - - except Exception as ex: - try: - slogger.task[tid].exception("cannot cancel tensorflow annotation for task #{}".format(tid), exc_info=True) - except: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index 05c714e3..57ea9031 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -1,50 +1,51 @@ -click==6.7 -Django==2.2.10 -django-appconf==1.0.2 -django-auth-ldap==1.4.0 -django-cacheops==4.0.6 -django-compressor==2.2 -django-rq==2.0.0 -EasyProcess==0.2.3 -Pillow==6.2.0 -numpy==1.16.2 -python-ldap==3.0.0 -pytz==2018.3 -pyunpack==0.1.2 +click==7.1.2 +Django==2.2.13 +django-appconf==1.0.4 +django-auth-ldap==2.2.0 +django-cacheops==5.0.1 +django-compressor==2.4 +django-rq==2.3.2 +EasyProcess==0.3 +Pillow==7.2.0 +numpy==1.18.5 +python-ldap==3.3.1 +pytz==2020.1 +pyhash==0.9.3 +pyunpack==0.2.1 rcssmin==1.0.6 -redis==3.2.0 -requests==2.20.0 -rjsmin==1.0.12 -rq==1.0.0 -rq-scheduler==0.9.1 -scipy==1.2.1 -sqlparse==0.2.4 +redis==3.5.3 +rjsmin==1.1.0 +requests==2.24.0 +rq==1.5.1 +rq-scheduler==0.10.0 +scipy==1.4.1 +sqlparse==0.3.1 django-sendfile==0.3.11 -dj-pagination==2.4.0 +dj-pagination==2.5.0 python-logstash==0.4.6 -django-revproxy==0.9.15 -rules==2.0 -GitPython==3.0.8 +django-revproxy==0.10.0 +rules==2.2 +GitPython==3.1.3 coreapi==2.3.3 -django-filter==2.0.0 -Markdown==3.0.1 -djangorestframework==3.9.3 -Pygments==2.3.1 -drf-yasg==1.17.0 -Shapely==1.6.4.post2 -pdf2image==1.6.0 +django-filter==2.3.0 +Markdown==3.2.2 +djangorestframework==3.11.1 +Pygments==2.6.1 +drf-yasg==1.17.1 +Shapely==1.7.1 +pdf2image==1.14.0 pascal_voc_writer==0.1.4 django-rest-auth[with_social]==0.9.5 -cython==0.29.13 +cython==0.29.21 matplotlib==3.0.3 scikit-image==0.15.0 -tensorflow==1.15.2 -keras==2.2.5 -opencv-python==4.1.0.25 -h5py==2.9.0 -imgaug==0.2.9 -django-cors-headers==3.2.0 -furl==2.0.0 +tensorflow==2.2.0 +keras==2.4.2 +opencv-python==4.4.0.42 +h5py==2.10.0 +imgaug==0.4.0 +django-cors-headers==3.4.0 +furl==2.1.0 av==6.2.0 # The package is used by pyunpack as a command line tool to support multiple # archives. Don't use as a python module because it has GPL license. diff --git a/cvat/requirements/development.txt b/cvat/requirements/development.txt index de046528..7efae677 100644 --- a/cvat/requirements/development.txt +++ b/cvat/requirements/development.txt @@ -1,13 +1,13 @@ -r base.txt -astroid==2.2.5 -isort==4.3.4 -lazy-object-proxy==1.3.1 +astroid==2.4.2 +isort==4.3.21 +lazy-object-proxy==1.5.1 mccabe==0.6.1 -pylint==2.3.1 -pylint-django==0.9.4 -pylint-plugin-utils==0.2.6 -rope==0.11 -wrapt==1.11.1 -django-extensions==2.0.6 -Werkzeug==0.15.3 -snakeviz==0.4.2 +pylint==2.6.0 +pylint-django==2.3.0 +pylint-plugin-utils==0.6 +rope==0.17.0 +wrapt==1.12.1 +django-extensions==3.0.5 +Werkzeug==1.0.1 +snakeviz==2.1.0 diff --git a/cvat/requirements/production.txt b/cvat/requirements/production.txt index 340e26be..36e64e29 100644 --- a/cvat/requirements/production.txt +++ b/cvat/requirements/production.txt @@ -1,3 +1,3 @@ -r base.txt -psycopg2-binary==2.7.4 -mod-wsgi==4.6.2 \ No newline at end of file +psycopg2-binary==2.8.5 +mod-wsgi==4.7.1 \ No newline at end of file diff --git a/cvat/requirements/staging.txt b/cvat/requirements/staging.txt index 6fefa087..bc8e8c2c 100644 --- a/cvat/requirements/staging.txt +++ b/cvat/requirements/staging.txt @@ -1,2 +1,2 @@ -r production.txt -django-silk==3.0.1 \ No newline at end of file +django-silk==4.1.0 \ No newline at end of file diff --git a/cvat/requirements/testing.txt b/cvat/requirements/testing.txt index 79c222a2..5ff78852 100644 --- a/cvat/requirements/testing.txt +++ b/cvat/requirements/testing.txt @@ -1,6 +1,6 @@ -r development.txt -fakeredis==1.1.0 +fakeredis==1.4.3 # Fix dependencies for fakeredis 1.1.0 # Pip will not reinstall six package if it is installed already -six==1.12.0 +six==1.15.0 coveralls \ No newline at end of file diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 29c6fe85..6a9a7a6a 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -82,10 +82,6 @@ try: except Exception: pass -# Application definition -JS_3RDPARTY = {} -CSS_3RDPARTY = {} - INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -99,6 +95,7 @@ INSTALLED_APPS = [ 'cvat.apps.engine', 'cvat.apps.git', 'cvat.apps.restrictions', + 'cvat.apps.lambda_manager', 'django_rq', 'compressor', 'cacheops', @@ -148,31 +145,21 @@ REST_FRAMEWORK = { # Disable default handling of the 'format' query parameter by REST framework 'URL_FORMAT_OVERRIDE': 'scheme', + 'DEFAULT_THROTTLE_CLASSES': [ + 'rest_framework.throttling.AnonRateThrottle', + ], + 'DEFAULT_THROTTLE_RATES': { + 'anon': '100/minute', + }, } REST_AUTH_REGISTER_SERIALIZERS = { 'REGISTER_SERIALIZER': 'cvat.apps.restrictions.serializers.RestrictedRegisterSerializer' } -if 'yes' == os.environ.get('TF_ANNOTATION', 'no'): - INSTALLED_APPS += ['cvat.apps.tf_annotation'] - -if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no'): - INSTALLED_APPS += ['cvat.apps.auto_annotation'] - -if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no') and os.environ.get('REID_MODEL_DIR', ''): - INSTALLED_APPS += ['cvat.apps.reid'] - -if 'yes' == os.environ.get('WITH_DEXTR', 'no'): - INSTALLED_APPS += ['cvat.apps.dextr_segmentation'] - if os.getenv('DJANGO_LOG_VIEWER_HOST'): INSTALLED_APPS += ['cvat.apps.log_viewer'] -# new feature by Mohammad -if 'yes' == os.environ.get('AUTO_SEGMENTATION', 'no'): - INSTALLED_APPS += ['cvat.apps.auto_segmentation'] - MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -218,17 +205,20 @@ WSGI_APPLICATION = 'cvat.wsgi.application' # Django Auth DJANGO_AUTH_TYPE = 'BASIC' DJANGO_AUTH_DEFAULT_GROUPS = [] -LOGIN_URL = 'login' +LOGIN_URL = 'rest_login' LOGIN_REDIRECT_URL = '/' -AUTH_LOGIN_NOTE = '

Have not registered yet? Register here.

' AUTHENTICATION_BACKENDS = [ 'rules.permissions.ObjectPermissionBackend', - 'django.contrib.auth.backends.ModelBackend' + 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend', ] # https://github.com/pennersr/django-allauth ACCOUNT_EMAIL_VERIFICATION = 'none' +# set UI url to redirect after a successful e-mail confirmation +ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = '/auth/login' +OLD_PASSWORD_FIELD_ENABLED = True # Django-RQ # https://github.com/rq/django-rq @@ -248,6 +238,13 @@ RQ_QUEUES = { } } +NUCLIO = { + 'SCHEME': 'http', + 'HOST': 'localhost', + 'PORT': 8070, + 'DEFAULT_TIMEOUT': 120 +} + RQ_SHOW_ADMIN_LINK = True RQ_EXCEPTION_HANDLERS = ['cvat.apps.engine.views.rq_handler'] diff --git a/cvat/settings/development.py b/cvat/settings/development.py index 40e597ee..0382d41e 100644 --- a/cvat/settings/development.py +++ b/cvat/settings/development.py @@ -37,6 +37,8 @@ UI_URL = '{}://{}'.format(UI_SCHEME, UI_HOST) if UI_PORT and UI_PORT != '80': UI_URL += ':{}'.format(UI_PORT) +# set UI url to redirect to after successful e-mail confirmation +ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = '{}/auth/login'.format(UI_URL) CORS_ORIGIN_WHITELIST = [UI_URL] CORS_REPLACE_HTTPS_REFERER = True diff --git a/cvat/settings/production.py b/cvat/settings/production.py index 4f919518..d6cea898 100644 --- a/cvat/settings/production.py +++ b/cvat/settings/production.py @@ -10,10 +10,12 @@ INSTALLED_APPS += [ 'mod_wsgi.server', ] +NUCLIO['HOST'] = os.getenv('CVAT_NUCLIO_HOST', 'nuclio') + for key in RQ_QUEUES: - RQ_QUEUES[key]['HOST'] = 'cvat_redis' + RQ_QUEUES[key]['HOST'] = os.getenv('CVAT_REDIS_HOST', 'cvat_redis') -CACHEOPS_REDIS['host'] = 'cvat_redis' +CACHEOPS_REDIS['host'] = os.getenv('CVAT_REDIS_HOST', 'cvat_redis') # Django-sendfile: # https://github.com/johnsensible/django-sendfile @@ -25,9 +27,9 @@ SENDFILE_BACKEND = 'sendfile.backends.xsendfile' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'HOST': 'cvat_db', - 'NAME': 'cvat', - 'USER': 'root', - 'PASSWORD': os.getenv('POSTGRES_PASSWORD', ''), + 'HOST': os.getenv('CVAT_POSTGRES_HOST', 'cvat_db'), + 'NAME': os.getenv('CVAT_POSTGRES_DBNAME', 'cvat'), + 'USER': os.getenv('CVAT_POSTGRES_USER', 'root'), + 'PASSWORD': os.getenv('CVAT_POSTGRES_PASSWORD', ''), } } diff --git a/cvat/urls.py b/cvat/urls.py index 6ae59f6b..db7e65d8 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -27,31 +27,17 @@ urlpatterns = [ path('admin/', admin.site.urls), path('', include('cvat.apps.engine.urls')), path('django-rq/', include('django_rq.urls')), - path('auth/', include('cvat.apps.authentication.urls')), path('documentation/', include('cvat.apps.documentation.urls')), ] -if apps.is_installed('cvat.apps.tf_annotation'): - urlpatterns.append(path('tensorflow/annotation/', include('cvat.apps.tf_annotation.urls'))) - if apps.is_installed('cvat.apps.git'): urlpatterns.append(path('git/repository/', include('cvat.apps.git.urls'))) -if apps.is_installed('cvat.apps.reid'): - urlpatterns.append(path('reid/', include('cvat.apps.reid.urls'))) - -if apps.is_installed('cvat.apps.auto_annotation'): - urlpatterns.append(path('auto_annotation/', include('cvat.apps.auto_annotation.urls'))) - -if apps.is_installed('cvat.apps.dextr_segmentation'): - urlpatterns.append(path('dextr/', include('cvat.apps.dextr_segmentation.urls'))) - if apps.is_installed('cvat.apps.log_viewer'): urlpatterns.append(path('analytics/', include('cvat.apps.log_viewer.urls'))) +if apps.is_installed('cvat.apps.lambda_manager'): + urlpatterns.append(path('', include('cvat.apps.lambda_manager.urls'))) + if apps.is_installed('silk'): urlpatterns.append(path('profiler/', include('silk.urls'))) - -# new feature by Mohammad -if apps.is_installed('cvat.apps.auto_segmentation'): - urlpatterns.append(path('tensorflow/segmentation/', include('cvat.apps.auto_segmentation.urls'))) diff --git a/cvat_proxy/conf.d/cvat.conf.template b/cvat_proxy/conf.d/cvat.conf.template index 084ce189..bd46d38a 100644 --- a/cvat_proxy/conf.d/cvat.conf.template +++ b/cvat_proxy/conf.d/cvat.conf.template @@ -12,24 +12,11 @@ server { proxy_set_header Host $http_host; proxy_pass_header Set-Cookie; - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { + location ~* /api/.*|git/.*|analytics/.*|static/.*|admin(?:/(.*))?.*|documentation/.*|django-rq(?:/(.*))? { proxy_pass http://cvat:8080; } - # workaround for match location by arguments - location = / { - error_page 418 = @annotation_ui; - - if ( $query_string ~ "^id=\d+.*" ) { return 418; } - proxy_pass http://cvat_ui; - } - location / { proxy_pass http://cvat_ui; } - - # old annotation ui, will be removed in the future. - location @annotation_ui { - proxy_pass http://cvat:8080; - } } diff --git a/datumaro/.gitignore b/datumaro/.gitignore new file mode 100644 index 00000000..17c3ea8b --- /dev/null +++ b/datumaro/.gitignore @@ -0,0 +1 @@ +/datumaro.egg-info diff --git a/datumaro/CONTRIBUTING.md b/datumaro/CONTRIBUTING.md index b2615298..97373b28 100644 --- a/datumaro/CONTRIBUTING.md +++ b/datumaro/CONTRIBUTING.md @@ -129,7 +129,7 @@ Plugins reside in plugin directories: - `/.datumaro/plugins` for project-specific components A plugin is a python file or package with any name, which exports some symbols. -To export a symbol put it to `exports` list of the module like this: +To export a symbol, put it to `exports` list of the module like this: ``` python class MyComponent1: ... diff --git a/datumaro/README.md b/datumaro/README.md index f7f1c46e..2d83cc4d 100644 --- a/datumaro/README.md +++ b/datumaro/README.md @@ -1,13 +1,13 @@ -# Dataset Framework (Datumaro) +# Dataset Management Framework (Datumaro) A framework to build, transform, and analyze datasets. ``` CVAT annotations -- ---> Annotation tool -... \ / + \ / COCO-like dataset -----> Datumaro ---> dataset ------> Model training -... / \ + / \ VOC-like dataset -- ---> Publication etc. ``` @@ -32,27 +32,38 @@ VOC-like dataset -- ---> Publication etc. - Dataset format conversions: - COCO (`image_info`, `instances`, `person_keypoints`, `captions`, `labels`*) - [Format specification](http://cocodataset.org/#format-data) + - [Dataset example](tests/assets/coco_dataset) - `labels` are our extension - like `instances` with only `category_id` - PASCAL VOC (`classification`, `detection`, `segmentation` (class, instances), `action_classification`, `person_layout`) - [Format specification](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/index.html) + - [Dataset example](tests/assets/voc_dataset) - YOLO (`bboxes`) - [Format specification](https://github.com/AlexeyAB/darknet#how-to-train-pascal-voc-data) + - [Dataset example](tests/assets/yolo_dataset) - TF Detection API (`bboxes`, `masks`) - Format specifications: [bboxes](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/using_your_own_dataset.md), [masks](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/instance_segmentation.md) + - [Dataset example](tests/assets/tf_detection_api_dataset) + - MOT sequences + - [Format specification](https://arxiv.org/pdf/1906.04567.pdf) + - [Dataset example](tests/assets/mot_dataset) - CVAT - [Format specification](https://github.com/opencv/cvat/blob/develop/cvat/apps/documentation/xml_format.md) + - [Dataset example](tests/assets/cvat_dataset) + - LabelMe + - [Format specification](http://labelme.csail.mit.edu/Release3.0) + - [Dataset example](tests/assets/labelme_dataset) - Dataset building operations: - Merging multiple datasets into one - Dataset filtering with custom conditions, for instance: - - remove all annotations except polygons of a certain class + - remove polygons of a certain class - remove images without a specific class - - remove occluded annotations from images + - remove `occluded` annotations from images - keep only vertically-oriented images - remove small area bounding boxes from annotations - - Annotation conversions, for instance + - Annotation conversions, for instance: - polygons to instance masks and vise-versa - apply a custom colormap for mask annotations - - remap dataset labels + - rename or remove dataset labels - Dataset comparison - Model integration: - Inference (OpenVINO and custom models) @@ -122,8 +133,8 @@ project = Project.load('directory') ```bash # Download VOC dataset: # http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar - datum project import --format voc --input-path - datum project export --format coco --filter '/item[annotation/label="cat"]' + datum convert --input-format voc --input-path \ + --output-format coco --filter '/item[annotation/label="cat"]' ``` - Convert only non-occluded annotations from a CVAT-annotated project to TFrecord: @@ -150,8 +161,8 @@ project = Project.load('directory') - Annotate instance polygons in CVAT, export as masks in COCO: ```bash - datum project import --format cvat --input-path - datum project export --format coco -- --segmentation-mode masks + datum convert --input-format cvat --input-path \ + --output-format coco -- --segmentation-mode masks ``` - Apply an OpenVINO detection model to some COCO-like dataset, @@ -166,6 +177,24 @@ project = Project.load('directory') datum project diff mymodel_inference/ --format tensorboard --output-dir diff ``` +- Change colors in PASCAL VOC-like `.png` masks: + ```bash + datum project import --format voc --input-path + + # Create a color map file with desired colors: + # + # label : color_rgb : parts : actions + # cat:0,0,255:: + # dog:255,0,0:: + # + # Save as mycolormap.txt + + datum project export --format voc_segmentation -- --label-map mycolormap.txt + # add "--apply-colormap=0" to save grayscale (indexed) masks + # check "--help" option for more info + # use "datum --loglevel debug" for extra conversion info + ``` + diff --git a/datumaro/datumaro/cli/__main__.py b/datumaro/datumaro/cli/__main__.py index a7d7dd99..fabe43f8 100644 --- a/datumaro/datumaro/cli/__main__.py +++ b/datumaro/datumaro/cli/__main__.py @@ -68,6 +68,8 @@ def make_parser(): ('remove', commands.remove, "Remove source from project"), ('export', commands.export, "Export project"), ('explain', commands.explain, "Run Explainable AI algorithm for model"), + ('merge', commands.merge, "Merge datasets"), + ('convert', commands.convert, "Convert dataset"), ] # Argparse doesn't support subparser groups: diff --git a/datumaro/datumaro/cli/commands/__init__.py b/datumaro/datumaro/cli/commands/__init__.py index 7656b7ef..7249842e 100644 --- a/datumaro/datumaro/cli/commands/__init__.py +++ b/datumaro/datumaro/cli/commands/__init__.py @@ -3,4 +3,4 @@ # # SPDX-License-Identifier: MIT -from . import add, create, explain, export, remove \ No newline at end of file +from . import add, create, explain, export, remove, merge, convert diff --git a/datumaro/datumaro/cli/commands/convert.py b/datumaro/datumaro/cli/commands/convert.py new file mode 100644 index 00000000..d867614d --- /dev/null +++ b/datumaro/datumaro/cli/commands/convert.py @@ -0,0 +1,137 @@ + +# Copyright (C) 2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import logging as log +import os +import os.path as osp + +from datumaro.components.project import Environment + +from ..contexts.project import FilterModes +from ..util import CliException, MultilineFormatter, make_file_name +from ..util.project import generate_next_file_name + + +def build_parser(parser_ctor=argparse.ArgumentParser): + builtin_importers = sorted(Environment().importers.items) + builtin_converters = sorted(Environment().converters.items) + + parser = parser_ctor(help="Convert an existing dataset to another format", + description=""" + Converts a dataset from one format to another. + You can add your own formats using a project.|n + |n + Supported input formats: %s|n + |n + Supported output formats: %s|n + |n + Examples:|n + - Export a dataset as a PASCAL VOC dataset, include images:|n + |s|sconvert -i src/path -f voc -- --save-images|n + |n + - Export a dataset as a COCO dataset to a specific directory:|n + |s|sconvert -i src/path -f coco -o path/I/like/ + """ % (', '.join(builtin_importers), ', '.join(builtin_converters)), + formatter_class=MultilineFormatter) + + parser.add_argument('-i', '--input-path', default='.', dest='source', + help="Path to look for a dataset") + parser.add_argument('-if', '--input-format', + help="Input dataset format. Will try to detect, if not specified.") + parser.add_argument('-f', '--output-format', required=True, + help="Output format") + parser.add_argument('-o', '--output-dir', dest='dst_dir', + help="Directory to save output (default: a subdir in the current one)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.add_argument('-e', '--filter', + help="Filter expression for dataset items") + parser.add_argument('--filter-mode', default=FilterModes.i.name, + type=FilterModes.parse, + help="Filter mode (options: %s; default: %s)" % \ + (', '.join(FilterModes.list_options()) , '%(default)s')) + parser.add_argument('extra_args', nargs=argparse.REMAINDER, + help="Additional arguments for output format (pass '-- -h' for help)") + parser.set_defaults(command=convert_command) + + return parser + +def convert_command(args): + env = Environment() + + try: + converter = env.converters.get(args.output_format) + except KeyError: + raise CliException("Converter for format '%s' is not found" % \ + args.output_format) + extra_args = converter.from_cmdline(args.extra_args) + def converter_proxy(extractor, save_dir): + return converter.convert(extractor, save_dir, **extra_args) + + filter_args = FilterModes.make_filter_args(args.filter_mode) + + if not args.input_format: + matches = [] + for format_name in env.importers.items: + log.debug("Checking '%s' format...", format_name) + importer = env.make_importer(format_name) + try: + match = importer.detect(args.source) + if match: + log.debug("format matched") + matches.append((format_name, importer)) + except NotImplementedError: + log.debug("Format '%s' does not support auto detection.", + format_name) + + if len(matches) == 0: + log.error("Failed to detect dataset format. " + "Try to specify format with '-if/--input-format' parameter.") + return 1 + elif len(matches) != 1: + log.error("Multiple formats match the dataset: %s. " + "Try to specify format with '-if/--input-format' parameter.", + ', '.join(m[0] for m in matches)) + return 2 + + format_name, importer = matches[0] + args.input_format = format_name + log.info("Source dataset format detected as '%s'", args.input_format) + else: + try: + importer = env.make_importer(args.input_format) + if hasattr(importer, 'from_cmdline'): + extra_args = importer.from_cmdline() + except KeyError: + raise CliException("Importer for format '%s' is not found" % \ + args.input_format) + + source = osp.abspath(args.source) + + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite to overwrite)" % dst_dir) + else: + dst_dir = generate_next_file_name('%s-%s' % \ + (osp.basename(source), make_file_name(args.output_format))) + dst_dir = osp.abspath(dst_dir) + + project = importer(source) + dataset = project.make_dataset() + + log.info("Exporting the dataset") + dataset.export_project( + save_dir=dst_dir, + converter=converter_proxy, + filter_expr=args.filter, + **filter_args) + + log.info("Dataset exported to '%s' as '%s'" % \ + (dst_dir, args.output_format)) + + return 0 diff --git a/datumaro/datumaro/cli/commands/explain.py b/datumaro/datumaro/cli/commands/explain.py index 9b5a6432..a0a5f1cc 100644 --- a/datumaro/datumaro/cli/commands/explain.py +++ b/datumaro/datumaro/cli/commands/explain.py @@ -59,7 +59,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser): help="Confidence threshold for detections (default: include all)") rise_parser.add_argument('-b', '--batch-size', default=1, type=int, help="Inference batch size (default: %(default)s)") - rise_parser.add_argument('--progressive', action='store_true', + rise_parser.add_argument('--display', action='store_true', help="Visualize results during computations") parser.add_argument('-p', '--project', dest='project_dir', default='.', @@ -108,16 +108,13 @@ def explain_command(args): if args.target[0] == TargetKinds.image: image_path = args.target[1] image = load_image(image_path) - if model.preferred_input_size() is not None: - h, w = model.preferred_input_size() - image = cv2.resize(image, (w, h)) log.info("Running inference explanation for '%s'" % image_path) - heatmap_iter = rise.apply(image, progressive=args.progressive) + heatmap_iter = rise.apply(image, progressive=args.display) image = image / 255.0 file_name = osp.splitext(osp.basename(image_path))[0] - if args.progressive: + if args.display: for i, heatmaps in enumerate(heatmap_iter): for j, heatmap in enumerate(heatmaps): hm_painted = cm.jet(heatmap)[:, :, 2::-1] @@ -154,35 +151,31 @@ def explain_command(args): log.info("Running inference explanation for '%s'" % project_name) for item in dataset: - image = item.image + image = item.image.data if image is None: log.warn( "Dataset item %s does not have image data. Skipping." % \ (item.id)) continue - if model.preferred_input_size() is not None: - h, w = model.preferred_input_size() - image = cv2.resize(image, (w, h)) heatmap_iter = rise.apply(image) image = image / 255.0 - file_name = osp.splitext(osp.basename(image_path))[0] heatmaps = next(heatmap_iter) if args.save_dir is not None: - log.info("Saving inference heatmaps at '%s'" % args.save_dir) + log.info("Saving inference heatmaps to '%s'" % args.save_dir) os.makedirs(args.save_dir, exist_ok=True) for j, heatmap in enumerate(heatmaps): - save_path = osp.join(args.save_dir, - file_name + '-heatmap-%s.png' % j) - save_image(save_path, heatmap * 255.0) + save_image(osp.join(args.save_dir, + item.id + '-heatmap-%s.png' % j), + heatmap * 255.0, create_dir=True) - if args.progressive: + if not args.save_dir or args.display: for j, heatmap in enumerate(heatmaps): disp = (image + cm.jet(heatmap)[:, :, 2::-1]) / 2 - cv2.imshow(file_name + '-heatmap-%s' % j, disp) + cv2.imshow(item.id + '-heatmap-%s' % j, disp) cv2.waitKey(0) else: raise NotImplementedError() diff --git a/datumaro/datumaro/cli/commands/merge.py b/datumaro/datumaro/cli/commands/merge.py new file mode 100644 index 00000000..2583cd86 --- /dev/null +++ b/datumaro/datumaro/cli/commands/merge.py @@ -0,0 +1,124 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import json +import logging as log +import os.path as osp +from collections import OrderedDict + +from datumaro.components.project import Project +from datumaro.components.operations import (IntersectMerge, + QualityError, MergeError) + +from ..util import at_least, MultilineFormatter, CliException +from ..util.project import generate_next_file_name, load_project + + +def build_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Merge few projects", + description=""" + Merges multiple datasets into one. This can be useful if you + have few annotations and wish to merge them, + taking into consideration potential overlaps and conflicts. + This command can try to find a common ground by voting or + return a list of conflicts.|n + |n + Examples:|n + - Merge annotations from 3 (or more) annotators:|n + |s|smerge project1/ project2/ project3/|n + - Check groups of the merged dataset for consistence:|n + |s|s|slook for groups consising of 'person', 'hand' 'head', 'foot'|n + |s|smerge project1/ project2/ -g 'person,hand?,head,foot?' + """, + formatter_class=MultilineFormatter) + + def _group(s): + return s.split(',') + + parser.add_argument('project', nargs='+', action=at_least(2), + help="Path to a project (repeatable)") + parser.add_argument('-iou', '--iou-thresh', default=0.25, type=float, + help="IoU match threshold for segments (default: %(default)s)") + parser.add_argument('-oconf', '--output-conf-thresh', + default=0.0, type=float, + help="Confidence threshold for output " + "annotations (default: %(default)s)") + parser.add_argument('--quorum', default=0, type=int, + help="Minimum count for a label and attribute voting " + "results to be counted (default: %(default)s)") + parser.add_argument('-g', '--groups', action='append', type=_group, + default=[], + help="A comma-separated list of labels in " + "annotation groups to check. '?' postfix can be added to a label to" + "make it optional in the group (repeatable)") + parser.add_argument('-o', '--output-dir', dest='dst_dir', default=None, + help="Output directory (default: current project's dir)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite existing files in the save directory") + parser.set_defaults(command=merge_command) + + return parser + +def merge_command(args): + source_projects = [load_project(p) for p in args.project] + + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite to overwrite)" % dst_dir) + else: + dst_dir = generate_next_file_name('merged') + + source_datasets = [] + for p in source_projects: + log.debug("Loading project '%s' dataset", p.config.project_name) + source_datasets.append(p.make_dataset()) + + merger = IntersectMerge(conf=IntersectMerge.Conf( + pairwise_dist=args.iou_thresh, groups=args.groups, + output_conf_thresh=args.output_conf_thresh, quorum=args.quorum + )) + merged_dataset = merger(source_datasets) + + merged_project = Project() + output_dataset = merged_project.make_dataset() + output_dataset.define_categories(merged_dataset.categories()) + merged_dataset = output_dataset.update(merged_dataset) + merged_dataset.save(save_dir=dst_dir) + + report_path = osp.join(dst_dir, 'merge_report.json') + save_merge_report(merger, report_path) + + dst_dir = osp.abspath(dst_dir) + log.info("Merge results have been saved to '%s'" % dst_dir) + log.info("Report has been saved to '%s'" % report_path) + + return 0 + +def save_merge_report(merger, path): + item_errors = OrderedDict() + source_errors = OrderedDict() + all_errors = [] + + for e in merger.errors: + if isinstance(e, QualityError): + item_errors[str(e.item_id)] = item_errors.get(str(e.item_id), 0) + 1 + elif isinstance(e, MergeError): + for s in e.sources: + source_errors[s] = source_errors.get(s, 0) + 1 + item_errors[str(e.item_id)] = item_errors.get(str(e.item_id), 0) + 1 + + all_errors.append(str(e)) + + errors = OrderedDict([ + ('Item errors', item_errors), + ('Source errors', source_errors), + ('All errors', all_errors), + ]) + + with open(path, 'w') as f: + json.dump(errors, f, indent=4) \ No newline at end of file diff --git a/datumaro/datumaro/cli/contexts/model/__init__.py b/datumaro/datumaro/cli/contexts/model/__init__.py index 5f40bd38..0c4f2018 100644 --- a/datumaro/datumaro/cli/contexts/model/__init__.py +++ b/datumaro/datumaro/cli/contexts/model/__init__.py @@ -7,45 +7,39 @@ import argparse import logging as log import os import os.path as osp -import shutil +import re from datumaro.components.config import DEFAULT_FORMAT -from ...util import add_subparser -from ...util.project import load_project - - -def build_openvino_add_parser(parser=argparse.ArgumentParser()): - parser.add_argument('-d', '--description', required=True, - help="Path to the model description file (.xml)") - parser.add_argument('-w', '--weights', required=True, - help="Path to the model weights file (.bin)") - parser.add_argument('-i', '--interpretation-script', required=True, - help="Path to the network output interpretation script (.py)") - parser.add_argument('--plugins-path', default=None, - help="Path to the custom Inference Engine plugins directory") - parser.add_argument('--copy', action='store_true', - help="Copy the model data to the project") +from datumaro.components.project import Environment - return parser +from ...util import CliException, MultilineFormatter, add_subparser +from ...util.project import load_project, \ + generate_next_name, generate_next_file_name -def openvino_args_extractor(args): - my_args = argparse.Namespace() - my_args.description = args.description - my_args.weights = args.weights - my_args.interpretation_script = args.interpretation_script - my_args.plugins_path = args.plugins_path - return my_args def build_add_parser(parser_ctor=argparse.ArgumentParser): - parser = parser_ctor() - - parser.add_argument('name', - help="Name of the model to be added") - launchers_sp = parser.add_subparsers(dest='launcher') - - build_openvino_add_parser(launchers_sp.add_parser('openvino')) \ - .set_defaults(launcher_args_extractor=openvino_args_extractor) - + builtins = sorted(Environment().launchers.items) + + parser = parser_ctor(help="Add model to project", + description=""" + Registers an executable model into a project. A model requires + a launcher to be executed. Each launcher has its own options, which + are passed after '--' separator, pass '-- -h' for more info. + |n + List of builtin launchers: %s + """ % ', '.join(builtins), + formatter_class=MultilineFormatter) + + parser.add_argument('-l', '--launcher', required=True, + help="Model launcher") + parser.add_argument('extra_args', nargs=argparse.REMAINDER, default=None, + help="Additional arguments for converter (pass '-- -h' for help)") + parser.add_argument('--copy', action='store_true', + help="Copy the model to the project") + parser.add_argument('-n', '--name', default=None, + help="Name of the model to be added (default: generate automatically)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite if exists") parser.add_argument('-p', '--project', dest='project_dir', default='.', help="Directory of the project to operate on (default: current dir)") parser.set_defaults(command=add_command) @@ -55,44 +49,47 @@ def build_add_parser(parser_ctor=argparse.ArgumentParser): def add_command(args): project = load_project(args.project_dir) - log.info("Adding '%s' model to '%s' project" % \ - (args.launcher, project.config.project_name)) - - options = args.launcher_args_extractor(args) + if args.name: + if not args.overwrite and args.name in project.config.models: + raise CliException("Model '%s' already exists " + "(pass --overwrite to overwrite)" % args.name) + else: + args.name = generate_next_name( + project.config.models, 'model', '-', default=0) + assert args.name not in project.config.models, args.name - if args.launcher == 'openvino' and args.copy: - config = project.config - env_config = project.env.config + try: + launcher = project.env.launchers.get(args.launcher) + except KeyError: + raise CliException("Launcher '%s' is not found" % args.launcher) - model_dir_rel = osp.join( - config.env_dir, env_config.models_dir, args.name) - model_dir = osp.join( - config.project_dir, model_dir_rel) + cli_plugin = getattr(launcher, 'cli_plugin', launcher) + model_args = cli_plugin.from_cmdline(args.extra_args) - os.makedirs(model_dir, exist_ok=True) + if args.copy: + log.info("Copying model data") - shutil.copy(options.description, - osp.join(model_dir, osp.basename(options.description))) - options.description = \ - osp.join(model_dir_rel, osp.basename(options.description)) + model_dir = project.local_model_dir(args.name) + os.makedirs(model_dir, exist_ok=False) - shutil.copy(options.weights, - osp.join(model_dir, osp.basename(options.weights))) - options.weights = \ - osp.join(model_dir_rel, osp.basename(options.weights)) - - shutil.copy(options.interpretation_script, - osp.join(model_dir, osp.basename(options.interpretation_script))) - options.interpretation_script = \ - osp.join(model_dir_rel, osp.basename(options.interpretation_script)) + try: + cli_plugin.copy_model(model_dir, model_args) + except (AttributeError, NotImplementedError): + log.error("Can't copy: copying is not available for '%s' models" % \ + args.launcher) + log.info("Checking the model") project.add_model(args.name, { 'launcher': args.launcher, - 'options': vars(options), + 'options': model_args, }) + project.make_executable_model(args.name) project.save() + log.info("Model '%s' with launcher '%s' has been added to project '%s'" % \ + (args.name, args.launcher, project.config.project_name)) + return 0 def build_remove_parser(parser_ctor=argparse.ArgumentParser): @@ -117,12 +114,14 @@ def remove_command(args): def build_run_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor() - parser.add_argument('-o', '--output-dir', dest='dst_dir', required=True, + parser.add_argument('-o', '--output-dir', dest='dst_dir', help="Directory to save output") parser.add_argument('-m', '--model', dest='model_name', required=True, help="Model to apply to the project") parser.add_argument('-p', '--project', dest='project_dir', default='.', help="Directory of the project to operate on (default: current dir)") + parser.add_argument('--overwrite', action='store_true', + help="Overwrite if exists") parser.set_defaults(command=run_command) return parser @@ -130,16 +129,47 @@ def build_run_parser(parser_ctor=argparse.ArgumentParser): def run_command(args): project = load_project(args.project_dir) - dst_dir = osp.abspath(args.dst_dir) - os.makedirs(dst_dir, exist_ok=False) + dst_dir = args.dst_dir + if dst_dir: + if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): + raise CliException("Directory '%s' already exists " + "(pass --overwrite overwrite)" % dst_dir) + else: + dst_dir = generate_next_file_name('%s-inference' % \ + project.config.project_name) + project.make_dataset().apply_model( - save_dir=dst_dir, - model_name=args.model_name) + save_dir=osp.abspath(dst_dir), + model=args.model_name) log.info("Inference results have been saved to '%s'" % dst_dir) return 0 +def build_info_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + + parser.add_argument('-n', '--name', + help="Model name") + parser.add_argument('-v', '--verbose', action='store_true', + help="Show details") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=info_command) + + return parser + +def info_command(args): + project = load_project(args.project_dir) + + if args.name: + model = project.get_model(args.name) + print(model) + else: + for name, conf in project.config.models.items(): + print(name) + if args.verbose: + print(dict(conf)) def build_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor() @@ -148,5 +178,6 @@ def build_parser(parser_ctor=argparse.ArgumentParser): add_subparser(subparsers, 'add', build_add_parser) add_subparser(subparsers, 'remove', build_remove_parser) add_subparser(subparsers, 'run', build_run_parser) + add_subparser(subparsers, 'info', build_info_parser) return parser diff --git a/datumaro/datumaro/cli/contexts/project/__init__.py b/datumaro/datumaro/cli/contexts/project/__init__.py index 4c443a2b..63c84076 100644 --- a/datumaro/datumaro/cli/contexts/project/__init__.py +++ b/datumaro/datumaro/cli/contexts/project/__init__.py @@ -5,6 +5,7 @@ import argparse from enum import Enum +import json import logging as log import os import os.path as osp @@ -16,10 +17,12 @@ from datumaro.components.comparator import Comparator from datumaro.components.dataset_filter import DatasetItemEncoder from datumaro.components.extractor import AnnotationType from datumaro.components.cli_plugin import CliPlugin +from datumaro.components.operations import \ + compute_image_statistics, compute_ann_statistics from .diff import DiffVisualizer from ...util import add_subparser, CliException, MultilineFormatter, \ make_file_name -from ...util.project import load_project, generate_next_dir_name +from ...util.project import load_project, generate_next_file_name def build_create_parser(parser_ctor=argparse.ArgumentParser): @@ -53,7 +56,7 @@ def create_command(args): if osp.isdir(project_env_dir) and os.listdir(project_env_dir): if not args.overwrite: raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % project_env_dir) + "(pass --overwrite to overwrite)" % project_env_dir) else: shutil.rmtree(project_env_dir, ignore_errors=True) @@ -61,7 +64,7 @@ def create_command(args): if osp.isdir(own_dataset_dir) and os.listdir(own_dataset_dir): if not args.overwrite: raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % own_dataset_dir) + "(pass --overwrite to overwrite)" % own_dataset_dir) else: # NOTE: remove the dir to avoid using data from previous project shutil.rmtree(own_dataset_dir) @@ -147,7 +150,7 @@ def import_command(args): if osp.isdir(project_env_dir) and os.listdir(project_env_dir): if not args.overwrite: raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % project_env_dir) + "(pass --overwrite to overwrite)" % project_env_dir) else: shutil.rmtree(project_env_dir, ignore_errors=True) @@ -155,7 +158,7 @@ def import_command(args): if osp.isdir(own_dataset_dir) and os.listdir(own_dataset_dir): if not args.overwrite: raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % own_dataset_dir) + "(pass --overwrite to overwrite)" % own_dataset_dir) else: # NOTE: remove the dir to avoid using data from previous project shutil.rmtree(own_dataset_dir) @@ -326,9 +329,9 @@ def export_command(args): if dst_dir: if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % dst_dir) + "(pass --overwrite to overwrite)" % dst_dir) else: - dst_dir = generate_next_dir_name('%s-%s' % \ + dst_dir = generate_next_file_name('%s-%s' % \ (project.config.project_name, make_file_name(args.format))) dst_dir = osp.abspath(dst_dir) @@ -338,9 +341,9 @@ def export_command(args): raise CliException("Converter for format '%s' is not found" % \ args.format) - if hasattr(converter, 'from_cmdline'): - extra_args = converter.from_cmdline(args.extra_args) - converter = converter(**extra_args) + extra_args = converter.from_cmdline(args.extra_args) + def converter_proxy(extractor, save_dir): + return converter.convert(extractor, save_dir, **extra_args) filter_args = FilterModes.make_filter_args(args.filter_mode) @@ -350,7 +353,7 @@ def export_command(args): log.info("Exporting the project...") dataset.export_project( save_dir=dst_dir, - converter=converter, + converter=converter_proxy, filter_expr=args.filter, **filter_args) log.info("Project exported to '%s' as '%s'" % \ @@ -422,9 +425,9 @@ def extract_command(args): if dst_dir: if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % dst_dir) + "(pass --overwrite to overwrite)" % dst_dir) else: - dst_dir = generate_next_dir_name('%s-filter' % \ + dst_dir = generate_next_file_name('%s-filter' % \ project.config.project_name) dst_dir = osp.abspath(dst_dir) @@ -451,10 +454,10 @@ def extract_command(args): return 0 def build_merge_parser(parser_ctor=argparse.ArgumentParser): - parser = parser_ctor(help="Merge projects", + parser = parser_ctor(help="Merge two projects", description=""" Updates items of the current project with items - from the other project.|n + from other project.|n |n Examples:|n - Update a project with items from other project:|n @@ -463,7 +466,7 @@ def build_merge_parser(parser_ctor=argparse.ArgumentParser): formatter_class=MultilineFormatter) parser.add_argument('other_project_dir', - help="Directory of the project to get data updates from") + help="Path to a project") parser.add_argument('-o', '--output-dir', dest='dst_dir', default=None, help="Output directory (default: current project's dir)") parser.add_argument('--overwrite', action='store_true', @@ -482,11 +485,12 @@ def merge_command(args): if dst_dir: if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % dst_dir) + "(pass --overwrite to overwrite)" % dst_dir) first_dataset = first_project.make_dataset() - first_dataset.update(second_project.make_dataset()) + second_dataset = second_project.make_dataset() + first_dataset.update(second_dataset) first_dataset.save(save_dir=dst_dir) if dst_dir is None: @@ -540,20 +544,26 @@ def diff_command(args): if dst_dir: if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % dst_dir) + "(pass --overwrite to overwrite)" % dst_dir) else: - dst_dir = generate_next_dir_name('%s-%s-diff' % ( + dst_dir = generate_next_file_name('%s-%s-diff' % ( first_project.config.project_name, second_project.config.project_name) ) dst_dir = osp.abspath(dst_dir) log.info("Saving diff to '%s'" % dst_dir) - visualizer = DiffVisualizer(save_dir=dst_dir, comparator=comparator, - output_format=args.format) - visualizer.save_dataset_diff( - first_project.make_dataset(), - second_project.make_dataset()) + dst_dir_existed = osp.exists(dst_dir) + try: + visualizer = DiffVisualizer(save_dir=dst_dir, comparator=comparator, + output_format=args.format) + visualizer.save_dataset_diff( + first_project.make_dataset(), + second_project.make_dataset()) + except BaseException: + if not dst_dir_existed and osp.isdir(dst_dir): + shutil.rmtree(dst_dir, ignore_errors=True) + raise return 0 @@ -569,7 +579,7 @@ def build_transform_parser(parser_ctor=argparse.ArgumentParser): |n Examples:|n - Convert instance polygons to masks:|n - |s|stransform -n polygons_to_masks + |s|stransform -t polygons_to_masks """ % ', '.join(builtins), formatter_class=MultilineFormatter) @@ -594,9 +604,9 @@ def transform_command(args): if dst_dir: if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): raise CliException("Directory '%s' already exists " - "(pass --overwrite to force creation)" % dst_dir) + "(pass --overwrite to overwrite)" % dst_dir) else: - dst_dir = generate_next_dir_name('%s-%s' % \ + dst_dir = generate_next_file_name('%s-%s' % \ (project.config.project_name, make_file_name(args.transform))) dst_dir = osp.abspath(dst_dir) @@ -623,6 +633,33 @@ def transform_command(args): return 0 +def build_stats_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor(help="Get project statistics", + description=""" + Outputs various project statistics like image mean and std, + annotations count etc. + """, + formatter_class=MultilineFormatter) + + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=stats_command) + + return parser + +def stats_command(args): + project = load_project(args.project_dir) + + dataset = project.make_dataset() + stats = {} + stats.update(compute_image_statistics(dataset)) + stats.update(compute_ann_statistics(dataset)) + + dst_file = generate_next_file_name('statistics', ext='.json') + log.info("Writing project statistics to '%s'" % dst_file) + with open(dst_file, 'w') as f: + json.dump(stats, f, indent=4, sort_keys=True) + def build_info_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor(help="Get project info", description=""" @@ -718,5 +755,6 @@ def build_parser(parser_ctor=argparse.ArgumentParser): add_subparser(subparsers, 'diff', build_diff_parser) add_subparser(subparsers, 'transform', build_transform_parser) add_subparser(subparsers, 'info', build_info_parser) + add_subparser(subparsers, 'stats', build_stats_parser) return parser diff --git a/datumaro/datumaro/cli/contexts/project/diff.py b/datumaro/datumaro/cli/contexts/project/diff.py index 78fdcd51..785c6c8e 100644 --- a/datumaro/datumaro/cli/contexts/project/diff.py +++ b/datumaro/datumaro/cli/contexts/project/diff.py @@ -83,11 +83,20 @@ class DiffVisualizer: if self.output_format is Format.tensorboard: self.file_writer.reopen() - for i, (item_a, item_b) in enumerate(zip(extractor_a, extractor_b)): - if item_a.id != item_b.id or not item_a.id or not item_b.id: - print("Dataset items #%s '%s' '%s' do not match" % \ - (i + 1, item_a.id, item_b.id)) - continue + ids_a = set((item.id, item.subset) for item in extractor_a) + ids_b = set((item.id, item.subset) for item in extractor_b) + ids = ids_a & ids_b + + if len(ids) != len(ids_a): + print("Unmatched items in the first dataset: ") + print(ids_a - ids) + if len(ids) != len(ids_b): + print("Unmatched items in the second dataset: ") + print(ids_b - ids) + + for item_id, item_subset in ids: + item_a = extractor_a.get(item_id, item_subset) + item_b = extractor_a.get(item_id, item_subset) label_diff = self.comparator.compare_item_labels(item_a, item_b) self.update_label_confusion(label_diff) @@ -220,10 +229,10 @@ class DiffVisualizer: img = np.hstack([img_a, img_b]) - path = osp.join(self.save_dir, 'diff_%s' % item_a.id) + path = osp.join(self.save_dir, item_a.id) if self.output_format is Format.simple: - save_image(path + '.png', img) + save_image(path + '.png', img, create_dir=True) elif self.output_format is Format.tensorboard: self.save_as_tensorboard(img, path) diff --git a/datumaro/datumaro/cli/contexts/source/__init__.py b/datumaro/datumaro/cli/contexts/source/__init__.py index 94734265..ef9edafb 100644 --- a/datumaro/datumaro/cli/contexts/source/__init__.py +++ b/datumaro/datumaro/cli/contexts/source/__init__.py @@ -225,6 +225,31 @@ def remove_command(args): return 0 +def build_info_parser(parser_ctor=argparse.ArgumentParser): + parser = parser_ctor() + + parser.add_argument('-n', '--name', + help="Source name") + parser.add_argument('-v', '--verbose', action='store_true', + help="Show details") + parser.add_argument('-p', '--project', dest='project_dir', default='.', + help="Directory of the project to operate on (default: current dir)") + parser.set_defaults(command=info_command) + + return parser + +def info_command(args): + project = load_project(args.project_dir) + + if args.name: + source = project.get_source(args.name) + print(source) + else: + for name, conf in project.config.sources.items(): + print(name) + if args.verbose: + print(dict(conf)) + def build_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor(description=""" Manipulate data sources inside of a project.|n @@ -243,5 +268,6 @@ def build_parser(parser_ctor=argparse.ArgumentParser): subparsers = parser.add_subparsers() add_subparser(subparsers, 'add', build_add_parser) add_subparser(subparsers, 'remove', build_remove_parser) + add_subparser(subparsers, 'info', build_info_parser) return parser diff --git a/datumaro/datumaro/cli/util/__init__.py b/datumaro/datumaro/cli/util/__init__.py index 2d04a693..3884b156 100644 --- a/datumaro/datumaro/cli/util/__init__.py +++ b/datumaro/datumaro/cli/util/__init__.py @@ -37,6 +37,28 @@ class MultilineFormatter(argparse.HelpFormatter): multiline_text += formatted_paragraph return multiline_text +def required_count(nmin=0, nmax=0): + assert 0 <= nmin and 0 <= nmax and nmin or nmax + + class RequiredCount(argparse.Action): + def __call__(self, parser, args, values, option_string=None): + k = len(values) + if not ((nmin and (nmin <= k) or not nmin) and \ + (nmax and (k <= nmax) or not nmax)): + msg = "Argument '%s' requires" % self.dest + if nmin and nmax: + msg += " from %s to %s arguments" % (nmin, nmax) + elif nmin: + msg += " at least %s arguments" % nmin + else: + msg += " no more %s arguments" % nmax + raise argparse.ArgumentTypeError(msg) + setattr(args, self.dest, values) + return RequiredCount + +def at_least(n): + return required_count(n, 0) + def make_file_name(s): # adapted from # https://docs.djangoproject.com/en/2.1/_modules/django/utils/text/#slugify diff --git a/datumaro/datumaro/cli/util/project.py b/datumaro/datumaro/cli/util/project.py index af92458b..75013053 100644 --- a/datumaro/datumaro/cli/util/project.py +++ b/datumaro/datumaro/cli/util/project.py @@ -4,31 +4,36 @@ # SPDX-License-Identifier: MIT import os +import re from datumaro.components.project import Project +from datumaro.util import cast def load_project(project_dir): return Project.load(project_dir) -def generate_next_dir_name(dirname, basedir='.', sep='.'): +def generate_next_file_name(basename, basedir='.', sep='.', ext=''): """ - If basedir does not contain dirname, returns dirname itself, - else generates a dirname by appending separator to the dirname + If basedir does not contain basename, returns basename, + otherwise generates a name by appending sep to the basename and the number, next to the last used number in the basedir for - files with dirname prefix. + files with basename prefix. Optionally, appends ext. """ - def _to_int(s): - try: - return int(s) - except Exception: - return 0 - sep_count = dirname.count(sep) + 2 - - files = [e for e in os.listdir(basedir) if e.startswith(dirname)] - if files: - files = [e.split(sep) for e in files] - files = [_to_int(e[-1]) for e in files if len(e) == sep_count] - dirname += '%s%s' % (sep, max(files, default=0) + 1) - return dirname \ No newline at end of file + return generate_next_name(os.listdir(basedir), basename, sep, ext) + +def generate_next_name(names, basename, sep='.', suffix='', default=None): + pattern = re.compile(r'%s(?:%s(\d+))?%s' % \ + tuple(map(re.escape, [basename, sep, suffix]))) + matches = [match for match in (pattern.match(n) for n in names) if match] + + max_idx = max([cast(match[1], int, 0) for match in matches], default=None) + if max_idx is None: + if default is not None: + idx = sep + str(default) + else: + idx = '' + else: + idx = sep + str(max_idx + 1) + return basename + idx + suffix \ No newline at end of file diff --git a/datumaro/datumaro/components/algorithms/rise.py b/datumaro/datumaro/components/algorithms/rise.py index 277bedd2..2f65c8cf 100644 --- a/datumaro/datumaro/components/algorithms/rise.py +++ b/datumaro/datumaro/components/algorithms/rise.py @@ -9,6 +9,7 @@ import numpy as np from math import ceil from datumaro.components.extractor import AnnotationType +from datumaro.util.annotation_util import nms def flatmatvec(mat): @@ -51,24 +52,6 @@ class RISE: bboxes.append(r) return labels, bboxes - @staticmethod - def nms(boxes, iou_thresh=0.5): - indices = np.argsort([b.attributes['score'] for b in boxes]) - ious = np.array([[a.iou(b) for b in boxes] for a in boxes]) - - predictions = [] - while len(indices) != 0: - i = len(indices) - 1 - pred_idx = indices[i] - to_remove = [i] - predictions.append(boxes[pred_idx]) - for i, box_idx in enumerate(indices[:i]): - if iou_thresh < ious[pred_idx, box_idx]: - to_remove.append(i) - indices = np.delete(indices, to_remove) - - return predictions - def normalize_hmaps(self, heatmaps, counts): eps = np.finfo(heatmaps.dtype).eps mhmaps = flatmatvec(heatmaps) @@ -106,7 +89,7 @@ class RISE: result_bboxes = [b for b in result_bboxes \ if self.det_conf_thresh <= b.attributes['score']] if 0 < self.nms_thresh: - result_bboxes = self.nms(result_bboxes, self.nms_thresh) + result_bboxes = nms(result_bboxes, self.nms_thresh) predicted_labels = set() if len(result_labels) != 0: @@ -194,7 +177,7 @@ class RISE: result_bboxes = [b for b in result_bboxes \ if self.det_conf_thresh <= b.attributes['score']] if 0 < self.nms_thresh: - result_bboxes = self.nms(result_bboxes, self.nms_thresh) + result_bboxes = nms(result_bboxes, self.nms_thresh) for detection in result_bboxes: for pred_idx, pred in enumerate(predicted_bboxes): @@ -202,7 +185,7 @@ class RISE: continue iou = pred.iou(detection) - assert 0 <= iou and iou <= 1 + assert iou == -1 or 0 <= iou and iou <= 1 if iou < iou_thresh: continue diff --git a/datumaro/datumaro/components/config.py b/datumaro/datumaro/components/config.py index 520c6e70..ca66eff8 100644 --- a/datumaro/datumaro/components/config.py +++ b/datumaro/datumaro/components/config.py @@ -130,7 +130,7 @@ class Config: return len(self.items()) def __iter__(self): - return iter(zip(self.keys(), self.values())) + return iter(self.keys()) def __getitem__(self, key): default = object() diff --git a/datumaro/datumaro/components/config_model.py b/datumaro/datumaro/components/config_model.py index 9bce725e..f46682d2 100644 --- a/datumaro/datumaro/components/config_model.py +++ b/datumaro/datumaro/components/config_model.py @@ -21,7 +21,6 @@ class Source(Config): MODEL_SCHEMA = _SchemaBuilder() \ .add('launcher', str) \ - .add('model_dir', str, internal=True) \ .add('options', dict) \ .build() diff --git a/datumaro/datumaro/components/converter.py b/datumaro/datumaro/components/converter.py index 9ea404d9..a7c6e101 100644 --- a/datumaro/datumaro/components/converter.py +++ b/datumaro/datumaro/components/converter.py @@ -3,17 +3,77 @@ # # SPDX-License-Identifier: MIT -class Converter: - def __init__(self, cmdline_args=None): - pass +import logging as log +import os +import os.path as osp +import shutil - def __call__(self, extractor, save_dir): - raise NotImplementedError() +from datumaro.components.cli_plugin import CliPlugin +from datumaro.util.image import save_image - def _parse_cmdline(self, cmdline): - parser = self.build_cmdline_parser() - if len(cmdline) != 0 and cmdline[0] == '--': - cmdline = cmdline[1:] - args = parser.parse_args(cmdline) - return vars(args) \ No newline at end of file +class IConverter: + @classmethod + def convert(cls, extractor, save_dir, **options): + raise NotImplementedError("Should be implemented in a subclass") + +class Converter(IConverter, CliPlugin): + DEFAULT_IMAGE_EXT = None + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--save-images', action='store_true', + help="Save images (default: %(default)s)") + parser.add_argument('--image-ext', default=None, + help="Image extension (default: keep or use format default%s)" % \ + (' ' + cls.DEFAULT_IMAGE_EXT if cls.DEFAULT_IMAGE_EXT else '')) + + return parser + + @classmethod + def convert(cls, extractor, save_dir, **options): + converter = cls(extractor, save_dir, **options) + return converter.apply() + + def apply(self): + raise NotImplementedError("Should be implemented in a subclass") + + def __init__(self, extractor, save_dir, save_images=False, + image_ext=None, default_image_ext=None): + default_image_ext = default_image_ext or self.DEFAULT_IMAGE_EXT + assert default_image_ext + self._default_image_ext = default_image_ext + + self._save_images = save_images + self._image_ext = image_ext + + self._extractor = extractor + self._save_dir = save_dir + + def _find_image_ext(self, item): + src_ext = None + if item.has_image: + src_ext = osp.splitext(osp.basename(item.image.path))[1] + + return self._image_ext or src_ext or self._default_image_ext + + def _make_image_filename(self, item): + return item.id + self._find_image_ext(item) + + def _save_image(self, item, path=None): + image = item.image.data + if image is None: + log.warning("Item '%s' has no image", item.id) + return item.image.path + + path = path or self._make_image_filename(item) + + src_ext = osp.splitext(osp.basename(item.image.path))[1] + dst_ext = osp.splitext(osp.basename(path))[1] + + os.makedirs(osp.dirname(path), exist_ok=True) + if src_ext == dst_ext and osp.isfile(item.image.path): + shutil.copyfile(item.image.path, path) + else: + save_image(path, image) diff --git a/datumaro/datumaro/components/dataset_filter.py b/datumaro/datumaro/components/dataset_filter.py index e9f10feb..e9fc5e35 100644 --- a/datumaro/datumaro/components/dataset_filter.py +++ b/datumaro/datumaro/components/dataset_filter.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: MIT import logging as log -from lxml import etree as ET # NOTE: lxml has proper XPath implementation +from lxml import etree as ET # lxml has proper XPath implementation from datumaro.components.extractor import (Transform, Annotation, AnnotationType, Label, Mask, Points, Polygon, PolyLine, Bbox, Caption, @@ -32,7 +32,12 @@ class DatasetItemEncoder: def encode_image(cls, image): image_elem = ET.Element('image') - h, w = image.size + size = image.size + if size is not None: + h, w = size + else: + h = 'unknown' + w = h ET.SubElement(image_elem, 'width').text = str(w) ET.SubElement(image_elem, 'height').text = str(h) diff --git a/datumaro/datumaro/components/extractor.py b/datumaro/datumaro/components/extractor.py index 1f18af0f..d7991cd1 100644 --- a/datumaro/datumaro/components/extractor.py +++ b/datumaro/datumaro/components/extractor.py @@ -7,7 +7,12 @@ from collections import namedtuple from enum import Enum import numpy as np +import attr +from attr import attrs, attrib + from datumaro.util.image import Image +from datumaro.util.attrs_util import not_empty, default_if_none + AnnotationType = Enum('AnnotationType', [ @@ -20,66 +25,64 @@ AnnotationType = Enum('AnnotationType', 'caption', ]) -class Annotation: - # pylint: disable=redefined-builtin - def __init__(self, id=None, type=None, attributes=None, group=None): - if id is not None: - id = int(id) - self.id = id +_COORDINATE_ROUNDING_DIGITS = 2 - assert type in AnnotationType - self.type = type +@attrs +class Annotation: + id = attrib(default=0, validator=default_if_none(int), kw_only=True) + attributes = attrib(factory=dict, validator=default_if_none(dict), kw_only=True) + group = attrib(default=0, validator=default_if_none(int), kw_only=True) - if attributes is None: - attributes = {} - else: - attributes = dict(attributes) - self.attributes = attributes + def __attrs_post_init__(self): + assert isinstance(self.type, AnnotationType) - if group is None: - group = 0 - else: - group = int(group) - self.group = group - # pylint: enable=redefined-builtin + @property + def type(self): + return self._type # must be set in subclasses - def __eq__(self, other): - if not isinstance(other, Annotation): - return False - return \ - (self.id == other.id) and \ - (self.type == other.type) and \ - (self.attributes == other.attributes) and \ - (self.group == other.group) + def wrap(item, **kwargs): + return attr.evolve(item, **kwargs) +@attrs class Categories: - def __init__(self, attributes=None): - if attributes is None: - attributes = set() - else: - if not isinstance(attributes, set): - attributes = set(attributes) - for attr in attributes: - assert isinstance(attr, str) - self.attributes = attributes - - def __eq__(self, other): - if not isinstance(other, Categories): - return False - return \ - (self.attributes == other.attributes) + attributes = attrib(factory=set, validator=default_if_none(set), + kw_only=True) +@attrs class LabelCategories(Categories): Category = namedtuple('Category', ['name', 'parent', 'attributes']) - def __init__(self, items=None, attributes=None): - super().__init__(attributes=attributes) + items = attrib(factory=list, validator=default_if_none(list)) + _indices = attrib(factory=dict, init=False, eq=False) + + @classmethod + def from_iterable(cls, iterable): + """Generation of LabelCategories from iterable object + + Args: + iterable ([type]): This iterable object can be: + 1)simple str - will generate one Category with str as name + 2)list of str - will interpreted as list of Category names + 3)list of positional argumetns - will generate Categories + with this arguments + + + Returns: + LabelCategories: LabelCategories object + """ + temp_categories = cls() + + if isinstance(iterable, str): + iterable = [[iterable]] + + for category in iterable: + if isinstance(category, str): + category = [category] + temp_categories.add(*category) - if items is None: - items = [] - self.items = items + return temp_categories - self._indices = {} + def __attrs_post_init__(self): self._reindex() def _reindex(self): @@ -90,7 +93,7 @@ class LabelCategories(Categories): self._indices = indices def add(self, name, parent=None, attributes=None): - assert name not in self._indices + assert name not in self._indices, name if attributes is None: attributes = set() else: @@ -108,53 +111,27 @@ class LabelCategories(Categories): def find(self, name): index = self._indices.get(name) - if index: + if index is not None: return index, self.items[index] return index, None - def __eq__(self, other): - if not super().__eq__(other): - return False - return \ - (self.items == other.items) - +@attrs class Label(Annotation): - # pylint: disable=redefined-builtin - def __init__(self, label=None, - id=None, attributes=None, group=None): - super().__init__(id=id, type=AnnotationType.label, - attributes=attributes, group=group) - - if label is not None: - label = int(label) - self.label = label - # pylint: enable=redefined-builtin - - def __eq__(self, other): - if not super().__eq__(other): - return False - return \ - (self.label == other.label) + _type = AnnotationType.label + label = attrib(converter=int) +@attrs(eq=False) class MaskCategories(Categories): - def __init__(self, colormap=None, inverse_colormap=None, attributes=None): - super().__init__(attributes=attributes) - - # colormap: label id -> color - if colormap is None: - colormap = {} - self.colormap = colormap - self._inverse_colormap = inverse_colormap + colormap = attrib(factory=dict, validator=default_if_none(dict)) + _inverse_colormap = attrib(default=None, + validator=attr.validators.optional(dict)) @property def inverse_colormap(self): from datumaro.util.mask_tools import invert_colormap if self._inverse_colormap is None: if self.colormap is not None: - try: - self._inverse_colormap = invert_colormap(self.colormap) - except Exception: - pass + self._inverse_colormap = invert_colormap(self.colormap) return self._inverse_colormap def __eq__(self, other): @@ -166,25 +143,13 @@ class MaskCategories(Categories): return False return True +@attrs(eq=False) class Mask(Annotation): - # pylint: disable=redefined-builtin - def __init__(self, image=None, label=None, z_order=None, - id=None, attributes=None, group=None): - super().__init__(type=AnnotationType.mask, - id=id, attributes=attributes, group=group) - - self._image = image - - if label is not None: - label = int(label) - self._label = label - - if z_order is None: - z_order = 0 - else: - z_order = int(z_order) - self._z_order = z_order - # pylint: enable=redefined-builtin + _type = AnnotationType.mask + _image = attrib() + label = attrib(converter=attr.converters.optional(int), + default=None, kw_only=True) + z_order = attrib(default=0, validator=default_if_none(int), kw_only=True) @property def image(self): @@ -192,14 +157,6 @@ class Mask(Annotation): return self._image() return self._image - @property - def label(self): - return self._label - - @property - def z_order(self): - return self._z_order - def as_class_mask(self, label_id=None): if label_id is None: label_id = self.label @@ -225,19 +182,14 @@ class Mask(Annotation): return \ (self.label == other.label) and \ (self.z_order == other.z_order) and \ - (self.image is not None and other.image is not None and \ - np.array_equal(self.image, other.image)) + (np.array_equal(self.image, other.image)) +@attrs(eq=False) class RleMask(Mask): - # pylint: disable=redefined-builtin - def __init__(self, rle=None, label=None, z_order=None, - id=None, attributes=None, group=None): - lazy_decode = self._lazy_decode(rle) - super().__init__(image=lazy_decode, label=label, z_order=z_order, - id=id, attributes=attributes, group=group) - - self._rle = rle - # pylint: enable=redefined-builtin + rle = attrib() + _image = attrib(default=attr.Factory( + lambda self: self._lazy_decode(self.rle), + takes_self=True), init=False) @staticmethod def _lazy_decode(rle): @@ -246,20 +198,16 @@ class RleMask(Mask): def get_area(self): from pycocotools import mask as mask_utils - return mask_utils.area(self._rle) + return mask_utils.area(self.rle) def get_bbox(self): from pycocotools import mask as mask_utils - return mask_utils.toBbox(self._rle) - - @property - def rle(self): - return self._rle + return mask_utils.toBbox(self.rle) def __eq__(self, other): if not isinstance(other, __class__): return super().__eq__(other) - return self._rle == other._rle + return self.rle == other.rle class CompiledMask: @staticmethod @@ -327,54 +275,13 @@ class CompiledMask: def lazy_extract(self, instance_id): return lambda: self.extract(instance_id) -def compute_iou(bbox_a, bbox_b): - aX, aY, aW, aH = bbox_a - bX, bY, bW, bH = bbox_b - in_right = min(aX + aW, bX + bW) - in_left = max(aX, bX) - in_top = max(aY, bY) - in_bottom = min(aY + aH, bY + bH) - - in_w = max(0, in_right - in_left) - in_h = max(0, in_bottom - in_top) - intersection = in_w * in_h - - a_area = aW * aH - b_area = bW * bH - union = a_area + b_area - intersection - - return intersection / max(1.0, union) - +@attrs class _Shape(Annotation): - # pylint: disable=redefined-builtin - def __init__(self, type, points=None, label=None, z_order=None, - id=None, attributes=None, group=None): - super().__init__(id=id, type=type, - attributes=attributes, group=group) - self._points = points - - if label is not None: - label = int(label) - self._label = label - - if z_order is None: - z_order = 0 - else: - z_order = int(z_order) - self._z_order = z_order - # pylint: enable=redefined-builtin - - @property - def points(self): - return self._points - - @property - def label(self): - return self._label - - @property - def z_order(self): - return self._z_order + points = attrib(converter=lambda x: + [round(p, _COORDINATE_ROUNDING_DIGITS) for p in x]) + label = attrib(converter=attr.converters.optional(int), + default=None, kw_only=True) + z_order = attrib(default=0, validator=default_if_none(int), kw_only=True) def get_area(self): raise NotImplementedError() @@ -392,22 +299,9 @@ class _Shape(Annotation): y1 = max(ys) return [x0, y0, x1 - x0, y1 - y0] - def __eq__(self, other): - if not super().__eq__(other): - return False - return \ - (np.array_equal(self.points, other.points)) and \ - (self.z_order == other.z_order) and \ - (self.label == other.label) - +@attrs class PolyLine(_Shape): - # pylint: disable=redefined-builtin - def __init__(self, points=None, label=None, z_order=None, - id=None, attributes=None, group=None): - super().__init__(type=AnnotationType.polyline, - points=points, label=label, z_order=z_order, - id=id, attributes=attributes, group=group) - # pylint: enable=redefined-builtin + _type = AnnotationType.polyline def as_polygon(self): return self.points[:] @@ -415,35 +309,33 @@ class PolyLine(_Shape): def get_area(self): return 0 +@attrs class Polygon(_Shape): - # pylint: disable=redefined-builtin - def __init__(self, points=None, label=None, - z_order=None, id=None, attributes=None, group=None): - if points is not None: - # keep the message on the single line to produce - # informative output - assert len(points) % 2 == 0 and 3 <= len(points) // 2, "Wrong polygon points: %s" % points - super().__init__(type=AnnotationType.polygon, - points=points, label=label, z_order=z_order, - id=id, attributes=attributes, group=group) - # pylint: enable=redefined-builtin + _type = AnnotationType.polygon + + def __attrs_post_init__(self): + super().__attrs_post_init__() + # keep the message on a single line to produce informative output + assert len(self.points) % 2 == 0 and 3 <= len(self.points) // 2, "Wrong polygon points: %s" % self.points def get_area(self): import pycocotools.mask as mask_utils - _, _, w, h = self.get_bbox() - rle = mask_utils.frPyObjects([self.points], h, w) + x, y, w, h = self.get_bbox() + rle = mask_utils.frPyObjects([self.points], y + h, x + w) area = mask_utils.area(rle)[0] return area +@attrs class Bbox(_Shape): - # pylint: disable=redefined-builtin - def __init__(self, x=0, y=0, w=0, h=0, label=None, z_order=None, - id=None, attributes=None, group=None): - super().__init__(type=AnnotationType.bbox, - points=[x, y, x + w, y + h], label=label, z_order=z_order, - id=id, attributes=attributes, group=group) - # pylint: enable=redefined-builtin + _type = AnnotationType.bbox + + # will be overridden by attrs, then will be overridden again by us + # attrs' method will be renamed to __attrs_init__ + def __init__(self, x, y, w, h, *args, **kwargs): + kwargs.pop('points', None) # comes from wrap() + self.__attrs_init__([x, y, x + w, y + h], *args, **kwargs) + __actual_init__ = __init__ # save pointer @property def x(self): @@ -477,17 +369,48 @@ class Bbox(_Shape): ] def iou(self, other): - return compute_iou(self.get_bbox(), other.get_bbox()) + from datumaro.util.annotation_util import bbox_iou + return bbox_iou(self.get_bbox(), other.get_bbox()) + + def wrap(item, **kwargs): + d = {'x': item.x, 'y': item.y, 'w': item.w, 'h': item.h} + d.update(kwargs) + return attr.evolve(item, **d) +assert not hasattr(Bbox, '__attrs_init__') # hopefully, it will be supported +setattr(Bbox, '__attrs_init__', Bbox.__init__) +setattr(Bbox, '__init__', Bbox.__actual_init__) + +@attrs class PointsCategories(Categories): Category = namedtuple('Category', ['labels', 'joints']) - def __init__(self, items=None, attributes=None): - super().__init__(attributes=attributes) + items = attrib(factory=dict, validator=default_if_none(dict)) - if items is None: - items = {} - self.items = items + @classmethod + def from_iterable(cls, iterable): + """Generation of PointsCategories from iterable object + + Args: + iterable ([type]): This iterable object can be: + 1)simple int - will generate one Category with int as label + 2)list of int - will interpreted as list of Category labels + 3)list of positional argumetns - will generate Categories + with this arguments + + Returns: + PointsCategories: PointsCategories object + """ + temp_categories = cls() + + if isinstance(iterable, int): + iterable = [[iterable]] + + for category in iterable: + if isinstance(category, int): + category = [category] + temp_categories.add(*category) + return temp_categories def add(self, label_id, labels=None, joints=None): if labels is None: @@ -497,41 +420,30 @@ class PointsCategories(Categories): joints = set(map(tuple, joints)) self.items[label_id] = self.Category(labels, joints) - def __eq__(self, other): - if not super().__eq__(other): - return False - return \ - (self.items == other.items) - +@attrs class Points(_Shape): Visibility = Enum('Visibility', [ ('absent', 0), ('hidden', 1), ('visible', 2), ]) + _type = AnnotationType.points - # pylint: disable=redefined-builtin - def __init__(self, points=None, visibility=None, label=None, z_order=None, - id=None, attributes=None, group=None): - if points is not None: - assert len(points) % 2 == 0 - - if visibility is not None: - assert len(visibility) == len(points) // 2 - for i, v in enumerate(visibility): - if not isinstance(v, self.Visibility): - visibility[i] = self.Visibility(v) - else: - visibility = [] - for _ in range(len(points) // 2): - visibility.append(self.Visibility.visible) - - super().__init__(type=AnnotationType.points, - points=points, label=label, z_order=z_order, - id=id, attributes=attributes, group=group) - + visibility = attrib(type=list, default=None) + @visibility.validator + def _visibility_validator(self, attribute, visibility): + if visibility is None: + visibility = [self.Visibility.visible] * (len(self.points) // 2) + else: + for i, v in enumerate(visibility): + if not isinstance(v, self.Visibility): + visibility[i] = self.Visibility(v) + assert len(visibility) == len(self.points) // 2 self.visibility = visibility - # pylint: enable=redefined-builtin + + def __attrs_post_init__(self): + super().__attrs_post_init__() + assert len(self.points) % 2 == 0, self.points def get_area(self): return 0 @@ -547,105 +459,37 @@ class Points(_Shape): y1 = max(ys, default=0) return [x0, y0, x1 - x0, y1 - y0] - def __eq__(self, other): - if not super().__eq__(other): - return False - return \ - (self.visibility == other.visibility) - +@attrs class Caption(Annotation): - # pylint: disable=redefined-builtin - def __init__(self, caption=None, - id=None, attributes=None, group=None): - super().__init__(id=id, type=AnnotationType.caption, - attributes=attributes, group=group) - - if caption is None: - caption = '' - else: - caption = str(caption) - self.caption = caption - # pylint: enable=redefined-builtin - - def __eq__(self, other): - if not super().__eq__(other): - return False - return \ - (self.caption == other.caption) + _type = AnnotationType.caption + caption = attrib(converter=str) +@attrs class DatasetItem: - # pylint: disable=redefined-builtin - def __init__(self, id=None, annotations=None, - subset=None, path=None, image=None): - assert id is not None - self._id = str(id) - - if subset is None: - subset = '' - else: - subset = str(subset) - self._subset = subset - - if path is None: - path = [] - else: - path = list(path) - self._path = path - - if annotations is None: - annotations = [] - else: - annotations = list(annotations) - self._annotations = annotations - + id = attrib(converter=lambda x: str(x).replace('\\', '/'), + type=str, validator=not_empty) + annotations = attrib(factory=list, validator=default_if_none(list)) + subset = attrib(default='', validator=default_if_none(str)) + path = attrib(factory=list, validator=default_if_none(list)) + + image = attrib(type=Image, default=None) + @image.validator + def _image_validator(self, attribute, image): if callable(image) or isinstance(image, np.ndarray): image = Image(data=image) elif isinstance(image, str): image = Image(path=image) assert image is None or isinstance(image, Image) - self._image = image - # pylint: enable=redefined-builtin + self.image = image - @property - def id(self): - return self._id - - @property - def subset(self): - return self._subset - - @property - def path(self): - return self._path - - @property - def annotations(self): - return self._annotations - - @property - def image(self): - return self._image + attributes = attrib(factory=dict, validator=default_if_none(dict)) @property def has_image(self): - return self._image is not None - - def __eq__(self, other): - if not isinstance(other, __class__): - return False - return \ - (self.id == other.id) and \ - (self.subset == other.subset) and \ - (self.path == other.path) and \ - (self.annotations == other.annotations) and \ - (self.image == other.image) + return self.image is not None def wrap(item, **kwargs): - expected_args = {'id', 'annotations', 'subset', 'path', 'image'} - for k in expected_args: - if k not in kwargs: - kwargs[k] = getattr(item, k) - return DatasetItem(**kwargs) + return attr.evolve(item, **kwargs) class IExtractor: def __iter__(self): diff --git a/datumaro/datumaro/components/launcher.py b/datumaro/datumaro/components/launcher.py index eb362955..b66bf237 100644 --- a/datumaro/datumaro/components/launcher.py +++ b/datumaro/datumaro/components/launcher.py @@ -5,7 +5,9 @@ import numpy as np -from datumaro.components.extractor import Transform +from datumaro.components.extractor import (Transform, LabelCategories, + AnnotationType) +from datumaro.util import take_by # pylint: disable=no-self-use @@ -16,45 +18,31 @@ class Launcher: def launch(self, inputs): raise NotImplementedError() - def preferred_input_size(self): - return None - - def get_categories(self): + def categories(self): return None # pylint: enable=no-self-use -class InferenceWrapper(Transform): +class ModelTransform(Transform): def __init__(self, extractor, launcher, batch_size=1): super().__init__(extractor) self._launcher = launcher self._batch_size = batch_size def __iter__(self): - stop = False - data_iter = iter(self._extractor) - while not stop: - batch_items = [] - try: - for _ in range(self._batch_size): - item = next(data_iter) - batch_items.append(item) - except StopIteration: - stop = True - if len(batch_items) == 0: - break - - inputs = np.array([item.image.data for item in batch_items]) + for batch in take_by(self._extractor, self._batch_size): + inputs = np.array([item.image.data for item in batch]) inference = self._launcher.launch(inputs) - for item, annotations in zip(batch_items, inference): + for item, annotations in zip(batch, inference): + self._check_annotations(annotations) yield self.wrap_item(item, annotations=annotations) def get_subset(self, name): subset = self._extractor.get_subset(name) - return InferenceWrapper(subset, self._launcher, self._batch_size) + return __class__(subset, self._launcher, self._batch_size) def categories(self): - launcher_override = self._launcher.get_categories() + launcher_override = self._launcher.categories() if launcher_override is not None: return launcher_override return self._extractor.categories() @@ -62,4 +50,18 @@ class InferenceWrapper(Transform): def transform_item(self, item): inputs = np.expand_dims(item.image, axis=0) annotations = self._launcher.launch(inputs)[0] - return self.wrap_item(item, annotations=annotations) \ No newline at end of file + return self.wrap_item(item, annotations=annotations) + + def _check_annotations(self, annotations): + labels_count = len(self.categories().get( + AnnotationType.label, LabelCategories()).items) + + for ann in annotations: + label = getattr(ann, 'label') + if label is None: + continue + + if label not in range(labels_count): + raise Exception("Annotation has unexpected label id %s, " + "while there is only %s defined labels." % \ + (label, labels_count)) \ No newline at end of file diff --git a/datumaro/datumaro/components/operations.py b/datumaro/datumaro/components/operations.py new file mode 100644 index 00000000..9e63d3a7 --- /dev/null +++ b/datumaro/datumaro/components/operations.py @@ -0,0 +1,1005 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from collections import OrderedDict +from copy import deepcopy +import logging as log + +import attr +import cv2 +import numpy as np +from attr import attrib, attrs + +from datumaro.components.cli_plugin import CliPlugin +from datumaro.components.extractor import AnnotationType, Bbox, Label +from datumaro.components.project import Dataset +from datumaro.util import find +from datumaro.util.attrs_util import ensure_cls +from datumaro.util.annotation_util import (segment_iou, bbox_iou, + mean_bbox, OKS, find_instances, max_bbox, smooth_line) + +def get_ann_type(anns, t): + return [a for a in anns if a.type == t] + +def match_annotations_equal(a, b): + matches = [] + a_unmatched = a[:] + b_unmatched = b[:] + for a_ann in a: + for b_ann in b_unmatched: + if a_ann != b_ann: + continue + + matches.append((a_ann, b_ann)) + a_unmatched.remove(a_ann) + b_unmatched.remove(b_ann) + break + + return matches, a_unmatched, b_unmatched + +def merge_annotations_equal(a, b): + matches, a_unmatched, b_unmatched = match_annotations_equal(a, b) + return [ann_a for (ann_a, _) in matches] + a_unmatched + b_unmatched + +def merge_categories(sources): + categories = {} + for source in sources: + categories.update(source) + for source in sources: + for cat_type, source_cat in source.items(): + if not categories[cat_type] == source_cat: + raise NotImplementedError( + "Merging different categories is not implemented yet") + return categories + +class MergingStrategy(CliPlugin): + @classmethod + def merge(cls, sources, **options): + instance = cls(**options) + return instance(sources) + + def __init__(self, **options): + super().__init__(**options) + self.__dict__['_sources'] = None + + def __call__(self, sources): + raise NotImplementedError() + + +@attrs +class DatasetError: + item_id = attrib() + +@attrs +class QualityError(DatasetError): + pass + +@attrs +class TooCloseError(QualityError): + a = attrib() + b = attrib() + distance = attrib() + + def __str__(self): + return "Item %s: annotations are too close: %s, %s, distance = %s" % \ + (self.item_id, self.a, self.b, self.distance) + +@attrs +class WrongGroupError(QualityError): + found = attrib(converter=set) + expected = attrib(converter=set) + group = attrib(converter=list) + + def __str__(self): + return "Item %s: annotation group has wrong labels: " \ + "found %s, expected %s, group %s" % \ + (self.item_id, self.found, self.expected, self.group) + +@attrs +class MergeError(DatasetError): + sources = attrib(converter=set) + +@attrs +class NoMatchingAnnError(MergeError): + ann = attrib() + + def __str__(self): + return "Item %s: can't find matching annotation " \ + "in sources %s, annotation is %s" % \ + (self.item_id, self.sources, self.ann) + +@attrs +class NoMatchingItemError(MergeError): + def __str__(self): + return "Item %s: can't find matching item in sources %s" % \ + (self.item_id, self.sources) + +@attrs +class FailedLabelVotingError(MergeError): + votes = attrib() + ann = attrib(default=None) + + def __str__(self): + return "Item %s: label voting failed%s, votes %s, sources %s" % \ + (self.item_id, 'for ann %s' % self.ann if self.ann else '', + self.votes, self.sources) + +@attrs +class FailedAttrVotingError(MergeError): + attr = attrib() + votes = attrib() + ann = attrib() + + def __str__(self): + return "Item %s: attribute voting failed " \ + "for ann %s, votes %s, sources %s" % \ + (self.item_id, self.ann, self.votes, self.sources) + +@attrs +class IntersectMerge(MergingStrategy): + @attrs(repr_ns='IntersectMerge', kw_only=True) + class Conf: + pairwise_dist = attrib(converter=float, default=0.5) + sigma = attrib(converter=list, factory=list) + + output_conf_thresh = attrib(converter=float, default=0) + quorum = attrib(converter=int, default=0) + ignored_attributes = attrib(converter=set, factory=set) + + def _groups_conveter(value): + result = [] + for group in value: + rg = set() + for label in group: + optional = label.endswith('?') + name = label if not optional else label[:-1] + rg.add((name, optional)) + result.append(rg) + return result + groups = attrib(converter=_groups_conveter, factory=list) + close_distance = attrib(converter=float, default=0.75) + conf = attrib(converter=ensure_cls(Conf), factory=Conf) + + # Error trackers: + errors = attrib(factory=list, init=False) + def add_item_error(self, error, *args, **kwargs): + self.errors.append(error(self._item_id, *args, **kwargs)) + + # Indexes: + _dataset_map = attrib(init=False) # id(dataset) -> (dataset, index) + _item_map = attrib(init=False) # id(item) -> (item, id(dataset)) + _ann_map = attrib(init=False) # id(ann) -> (ann, id(item)) + _item_id = attrib(init=False) + _item = attrib(init=False) + + # Misc. + _categories = attrib(init=False) # merged categories + + def __call__(self, datasets): + self._categories = merge_categories(d.categories() for d in datasets) + merged = Dataset(categories=self._categories) + + self._check_groups_definition() + + item_matches, item_map = self.match_items(datasets) + self._item_map = item_map + self._dataset_map = { id(d): (d, i) for i, d in enumerate(datasets) } + + for item_id, items in item_matches.items(): + self._item_id = item_id + + if len(items) < len(datasets): + missing_sources = set(id(s) for s in datasets) - set(items) + missing_sources = [self._dataset_map[s][1] + for s in missing_sources] + self.add_item_error(NoMatchingItemError, missing_sources) + merged.put(self.merge_items(items)) + + return merged + + def get_ann_source(self, ann_id): + return self._item_map[self._ann_map[ann_id][1]][1] + + def merge_items(self, items): + self._item = next(iter(items.values())) + + self._ann_map = {} + sources = [] + for item in items.values(): + self._ann_map.update({ id(a): (a, id(item)) + for a in item.annotations }) + sources.append(item.annotations) + log.debug("Merging item %s: source annotations %s" % \ + (self._item_id, list(map(len, sources)))) + + annotations = self.merge_annotations(sources) + + annotations = [a for a in annotations + if self.conf.output_conf_thresh <= a.attributes.get('score', 1)] + + return self._item.wrap(annotations=annotations) + + def merge_annotations(self, sources): + self._make_mergers(sources) + + clusters = self._match_annotations(sources) + + joined_clusters = sum(clusters.values(), []) + group_map = self._find_cluster_groups(joined_clusters) + + annotations = [] + for t, clusters in clusters.items(): + for cluster in clusters: + self._check_cluster_sources(cluster) + + merged_clusters = self._merge_clusters(t, clusters) + + for merged_ann, cluster in zip(merged_clusters, clusters): + attributes = self._find_cluster_attrs(cluster, merged_ann) + attributes = { k: v for k, v in attributes.items() + if k not in self.conf.ignored_attributes } + attributes.update(merged_ann.attributes) + merged_ann.attributes = attributes + + new_group_id = find(enumerate(group_map), + lambda e: id(cluster) in e[1][0]) + if new_group_id is None: + new_group_id = 0 + else: + new_group_id = new_group_id[0] + 1 + merged_ann.group = new_group_id + + if self.conf.close_distance: + self._check_annotation_distance(t, merged_clusters) + + annotations += merged_clusters + + if self.conf.groups: + self._check_groups(annotations) + + return annotations + + @staticmethod + def match_items(datasets): + item_ids = set((item.id, item.subset) for d in datasets for item in d) + + item_map = {} # id(item) -> (item, id(dataset)) + + matches = OrderedDict() + for (item_id, item_subset) in sorted(item_ids, key=lambda e: e[0]): + items = {} + for d in datasets: + try: + item = d.get(item_id, subset=item_subset) + items[id(d)] = item + item_map[id(item)] = (item, id(d)) + except KeyError: + pass + matches[(item_id, item_subset)] = items + + return matches, item_map + + def _match_annotations(self, sources): + all_by_type = {} + for s in sources: + src_by_type = {} + for a in s: + src_by_type.setdefault(a.type, []).append(a) + for k, v in src_by_type.items(): + all_by_type.setdefault(k, []).append(v) + + clusters = {} + for k, v in all_by_type.items(): + clusters.setdefault(k, []).extend(self._match_ann_type(k, v)) + + return clusters + + def _make_mergers(self, sources): + def _make(c, **kwargs): + kwargs.update(attr.asdict(self.conf)) + fields = attr.fields_dict(c) + return c(**{ k: v for k, v in kwargs.items() if k in fields }, + context=self) + + def _for_type(t, **kwargs): + if t is AnnotationType.label: + return _make(LabelMerger, **kwargs) + elif t is AnnotationType.bbox: + return _make(BboxMerger, **kwargs) + elif t is AnnotationType.mask: + return _make(MaskMerger, **kwargs) + elif t is AnnotationType.polygon: + return _make(PolygonMerger, **kwargs) + elif t is AnnotationType.polyline: + return _make(LineMerger, **kwargs) + elif t is AnnotationType.points: + return _make(PointsMerger, **kwargs) + elif t is AnnotationType.caption: + return _make(CaptionsMerger, **kwargs) + else: + raise NotImplementedError("Type %s is not supported" % t) + + instance_map = {} + for s in sources: + s_instances = find_instances(s) + for inst in s_instances: + inst_bbox = max_bbox([a for a in inst if a.type in + {AnnotationType.polygon, + AnnotationType.mask, AnnotationType.bbox} + ]) + for ann in inst: + instance_map[id(ann)] = [inst, inst_bbox] + + self._mergers = { t: _for_type(t, instance_map=instance_map) + for t in AnnotationType } + + def _match_ann_type(self, t, sources): + return self._mergers[t].match_annotations(sources) + + def _merge_clusters(self, t, clusters): + return self._mergers[t].merge_clusters(clusters) + + @staticmethod + def _find_cluster_groups(clusters): + cluster_groups = [] + visited = set() + for a_idx, cluster_a in enumerate(clusters): + if a_idx in visited: + continue + visited.add(a_idx) + + cluster_group = { id(cluster_a) } + + # find segment groups in the cluster group + a_groups = set(ann.group for ann in cluster_a) + for cluster_b in clusters[a_idx+1 :]: + b_groups = set(ann.group for ann in cluster_b) + if a_groups & b_groups: + a_groups |= b_groups + + # now we know all the segment groups in this cluster group + # so we can find adjacent clusters + for b_idx, cluster_b in enumerate(clusters[a_idx+1 :]): + b_idx = a_idx + 1 + b_idx + b_groups = set(ann.group for ann in cluster_b) + if a_groups & b_groups: + cluster_group.add( id(cluster_b) ) + visited.add(b_idx) + + if a_groups == {0}: + continue # skip annotations without a group + cluster_groups.append( (cluster_group, a_groups) ) + return cluster_groups + + def _find_cluster_attrs(self, cluster, ann): + quorum = self.conf.quorum or 0 + + # TODO: when attribute types are implemented, add linear + # interpolation for contiguous values + + attr_votes = {} # name -> { value: score , ... } + for s in cluster: + for name, value in s.attributes.items(): + votes = attr_votes.get(name, {}) + votes[value] = 1 + votes.get(value, 0) + attr_votes[name] = votes + + attributes = {} + for name, votes in attr_votes.items(): + winner, count = max(votes.items(), key=lambda e: e[1]) + if count < quorum: + if sum(votes.values()) < quorum: + # blame provokers + missing_sources = set( + self.get_ann_source(id(a)) for a in cluster + if s.attributes.get(name) == winner) + else: + # blame outliers + missing_sources = set( + self.get_ann_source(id(a)) for a in cluster + if s.attributes.get(name) != winner) + missing_sources = [self._dataset_map[s][1] + for s in missing_sources] + self.add_item_error(FailedAttrVotingError, + missing_sources, name, votes, ann) + continue + attributes[name] = winner + + return attributes + + def _check_cluster_sources(self, cluster): + if len(cluster) == len(self._dataset_map): + return + + def _has_item(s): + try: + item =self._dataset_map[s][0].get(*self._item_id) + if len(item.annotations) == 0: + return False + return True + except KeyError: + return False + + missing_sources = set(self._dataset_map) - \ + set(self.get_ann_source(id(a)) for a in cluster) + missing_sources = [self._dataset_map[s][1] for s in missing_sources + if _has_item(s)] + if missing_sources: + self.add_item_error(NoMatchingAnnError, missing_sources, cluster[0]) + + def _check_annotation_distance(self, t, annotations): + for a_idx, a_ann in enumerate(annotations): + for b_ann in annotations[a_idx+1:]: + d = self._mergers[t].distance(a_ann, b_ann) + if self.conf.close_distance < d: + self.add_item_error(TooCloseError, a_ann, b_ann, d) + + def _check_groups(self, annotations): + check_groups = [] + for check_group_raw in self.conf.groups: + check_group = set(l[0] for l in check_group_raw) + optional = set(l[0] for l in check_group_raw if l[1]) + check_groups.append((check_group, optional)) + + def _check_group(group_labels, group): + for check_group, optional in check_groups: + common = check_group & group_labels + real_miss = check_group - common - optional + extra = group_labels - check_group + if common and (extra or real_miss): + self.add_item_error(WrongGroupError, group_labels, + check_group, group) + break + + groups = find_instances(annotations) + for group in groups: + group_labels = set() + for ann in group: + if not hasattr(ann, 'label'): + continue + label = self._get_label_name(ann.label) + + if ann.group: + group_labels.add(label) + else: + _check_group({label}, [ann]) + + if not group_labels: + continue + _check_group(group_labels, group) + + def _get_label_name(self, label_id): + return self._categories[AnnotationType.label].items[label_id].name + + def _check_groups_definition(self): + for group in self.conf.groups: + for label, _ in group: + _, entry = self._categories[AnnotationType.label].find(label) + if entry is None: + raise ValueError("Datasets do not contain " + "label '%s', available labels %s" % \ + (label, [i.name for i in + self._categories[AnnotationType.label].items]) + ) + +@attrs +class AnnotationMatcher: + def match_annotations(self, sources): + raise NotImplementedError() + +@attrs +class LabelMatcher(AnnotationMatcher): + @staticmethod + def distance(a, b): + return a.label == b.label + + def match_annotations(self, sources): + return [sum(sources, [])] + +@attrs(kw_only=True) +class _ShapeMatcher(AnnotationMatcher): + pairwise_dist = attrib(converter=float, default=0.9) + cluster_dist = attrib(converter=float, default=-1.0) + + def match_annotations(self, sources): + distance = self.distance + pairwise_dist = self.pairwise_dist + cluster_dist = self.cluster_dist + + if cluster_dist < 0: cluster_dist = pairwise_dist + + id_segm = { id(a): (a, id(s)) for s in sources for a in s } + + def _is_close_enough(cluster, extra_id): + # check if whole cluster IoU will not be broken + # when this segment is added + b = id_segm[extra_id][0] + for a_id in cluster: + a = id_segm[a_id][0] + if distance(a, b) < cluster_dist: + return False + return True + + def _has_same_source(cluster, extra_id): + b = id_segm[extra_id][1] + for a_id in cluster: + a = id_segm[a_id][1] + if a == b: + return True + return False + + # match segments in sources, pairwise + adjacent = { i: [] for i in id_segm } # id(sgm) -> [id(adj_sgm1), ...] + for a_idx, src_a in enumerate(sources): + for src_b in sources[a_idx+1 :]: + matches, _, _, _ = match_segments(src_a, src_b, + dist_thresh=pairwise_dist, distance=distance) + for m in matches: + adjacent[id(m[0])].append(id(m[1])) + + # join all segments into matching clusters + clusters = [] + visited = set() + for cluster_idx in adjacent: + if cluster_idx in visited: + continue + + cluster = set() + to_visit = { cluster_idx } + while to_visit: + c = to_visit.pop() + cluster.add(c) + visited.add(c) + + for i in adjacent[c]: + if i in visited: + continue + if 0 < cluster_dist and not _is_close_enough(cluster, i): + continue + if _has_same_source(cluster, i): + continue + + to_visit.add(i) + + clusters.append([id_segm[i][0] for i in cluster]) + + return clusters + + @staticmethod + def distance(a, b): + return segment_iou(a, b) + +@attrs +class BboxMatcher(_ShapeMatcher): + pass + +@attrs +class PolygonMatcher(_ShapeMatcher): + pass + +@attrs +class MaskMatcher(_ShapeMatcher): + pass + +@attrs(kw_only=True) +class PointsMatcher(_ShapeMatcher): + sigma = attrib(converter=list, default=None) + instance_map = attrib(converter=dict) + + def distance(self, a, b): + a_bbox = self.instance_map[id(a)][1] + b_bbox = self.instance_map[id(b)][1] + if bbox_iou(a_bbox, b_bbox) <= 0: + return 0 + bbox = mean_bbox([a_bbox, b_bbox]) + return OKS(a, b, sigma=self.sigma, bbox=bbox) + +@attrs +class LineMatcher(_ShapeMatcher): + @staticmethod + def distance(a, b): + a_bbox = a.get_bbox() + b_bbox = b.get_bbox() + bbox = max_bbox([a_bbox, b_bbox]) + area = bbox[2] * bbox[3] + if not area: + return 1 + + # compute inter-line area, normalize by common bbox + point_count = max(max(len(a.points) // 2, len(b.points) // 2), 5) + a, sa = smooth_line(a.points, point_count) + b, sb = smooth_line(b.points, point_count) + dists = np.linalg.norm(a - b, axis=1) + dists = (dists[:-1] + dists[1:]) * 0.5 + s = np.sum(dists) * 0.5 * (sa + sb) / area + return abs(1 - s) + +@attrs +class CaptionsMatcher(AnnotationMatcher): + def match_annotations(self, sources): + raise NotImplementedError() + + +@attrs(kw_only=True) +class AnnotationMerger: + _context = attrib(type=IntersectMerge, default=None) + + def merge_clusters(self, clusters): + raise NotImplementedError() + +@attrs(kw_only=True) +class LabelMerger(AnnotationMerger, LabelMatcher): + quorum = attrib(converter=int, default=0) + + def merge_clusters(self, clusters): + assert len(clusters) <= 1 + if len(clusters) == 0: + return [] + + votes = {} # label -> score + for label_ann in clusters[0]: + votes[label_ann.label] = 1 + votes.get(label_ann.label, 0) + + merged = [] + for label, count in votes.items(): + if count < self.quorum: + sources = set(self.get_ann_source(id(a)) for a in clusters[0] + if label not in [l.label for l in a]) + sources = [self._context._dataset_map[s][1] for s in sources] + self._context.add_item_error(FailedLabelVotingError, + sources, votes) + continue + + merged.append(Label(label, attributes={ + 'score': count / len(self._context._dataset_map) + })) + + return merged + +@attrs(kw_only=True) +class _ShapeMerger(AnnotationMerger, _ShapeMatcher): + quorum = attrib(converter=int, default=0) + + def merge_clusters(self, clusters): + merged = [] + for cluster in clusters: + label, label_score = self.find_cluster_label(cluster) + shape, shape_score = self.merge_cluster_shape(cluster) + + shape.z_order = max(cluster, key=lambda a: a.z_order).z_order + shape.label = label + shape.attributes['score'] = label_score * shape_score \ + if label is not None else shape_score + + merged.append(shape) + + return merged + + def find_cluster_label(self, cluster): + votes = {} + for s in cluster: + state = votes.setdefault(s.label, [0, 0]) + state[0] += s.attributes.get('score', 1.0) + state[1] += 1 + + label, (score, count) = max(votes.items(), key=lambda e: e[1][0]) + if count < self.quorum: + self._context.add_item_error(FailedLabelVotingError, votes) + score = score / count if count else None + return label, score + + @staticmethod + def _merge_cluster_shape_mean_box_nearest(cluster): + mbbox = Bbox(*mean_bbox(cluster)) + dist = (segment_iou(mbbox, s) for s in cluster) + nearest_pos, _ = max(enumerate(dist), key=lambda e: e[1]) + return cluster[nearest_pos] + + def merge_cluster_shape(self, cluster): + shape = self._merge_cluster_shape_mean_box_nearest(cluster) + shape_score = sum(max(0, self.distance(shape, s)) + for s in cluster) / len(cluster) + return shape, shape_score + +@attrs +class BboxMerger(_ShapeMerger, BboxMatcher): + pass + +@attrs +class PolygonMerger(_ShapeMerger, PolygonMatcher): + pass + +@attrs +class MaskMerger(_ShapeMerger, MaskMatcher): + pass + +@attrs +class PointsMerger(_ShapeMerger, PointsMatcher): + pass + +@attrs +class LineMerger(_ShapeMerger, LineMatcher): + pass + +@attrs +class CaptionsMerger(AnnotationMerger, CaptionsMatcher): + pass + +def match_segments(a_segms, b_segms, distance='iou', dist_thresh=1.0): + if distance == 'iou': + distance = segment_iou + else: + assert callable(distance) + + a_segms.sort(key=lambda ann: 1 - ann.attributes.get('score', 1)) + b_segms.sort(key=lambda ann: 1 - ann.attributes.get('score', 1)) + + # a_matches: indices of b_segms matched to a bboxes + # b_matches: indices of a_segms matched to b bboxes + a_matches = -np.ones(len(a_segms), dtype=int) + b_matches = -np.ones(len(b_segms), dtype=int) + + distances = np.array([[distance(a, b) for b in b_segms] for a in a_segms]) + + # matches: boxes we succeeded to match completely + # mispred: boxes we succeeded to match, having label mismatch + matches = [] + mispred = [] + + for a_idx, a_segm in enumerate(a_segms): + if len(b_segms) == 0: + break + matched_b = a_matches[a_idx] + max_dist = max(distances[a_idx, matched_b], dist_thresh) + for b_idx, b_segm in enumerate(b_segms): + if 0 <= b_matches[b_idx]: # assign a_segm with max conf + continue + d = distances[a_idx, b_idx] + if d < max_dist: + continue + max_dist = d + matched_b = b_idx + + if matched_b < 0: + continue + a_matches[a_idx] = matched_b + b_matches[matched_b] = a_idx + + b_segm = b_segms[matched_b] + + if a_segm.label == b_segm.label: + matches.append( (a_segm, b_segm) ) + else: + mispred.append( (a_segm, b_segm) ) + + # *_umatched: boxes of (*) we failed to match + a_unmatched = [a_segms[i] for i, m in enumerate(a_matches) if m < 0] + b_unmatched = [b_segms[i] for i, m in enumerate(b_matches) if m < 0] + + return matches, mispred, a_unmatched, b_unmatched + +def mean_std(dataset): + """ + Computes unbiased mean and std. dev. for dataset images, channel-wise. + """ + # Use an online algorithm to: + # - handle different image sizes + # - avoid cancellation problem + if len(dataset) == 0: + return [0, 0, 0], [0, 0, 0] + + stats = np.empty((len(dataset), 2, 3), dtype=np.double) + counts = np.empty(len(dataset), dtype=np.uint32) + + mean = lambda i, s: s[i][0] + var = lambda i, s: s[i][1] + + for i, item in enumerate(dataset): + counts[i] = np.prod(item.image.size) + + image = item.image.data + if len(image.shape) == 2: + image = image[:, :, np.newaxis] + else: + image = image[:, :, :3] + # opencv is much faster than numpy here + cv2.meanStdDev(image.astype(np.double) / 255, + mean=mean(i, stats), stddev=var(i, stats)) + + # make variance unbiased + np.multiply(np.square(stats[:, 1]), + (counts / (counts - 1))[:, np.newaxis], + out=stats[:, 1]) + + _, mean, var = StatsCounter().compute_stats(stats, counts, mean, var) + return mean * 255, np.sqrt(var) * 255 + +class StatsCounter: + # Implements online parallel computation of sample variance + # https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm + + # Needed do avoid catastrophic cancellation in floating point computations + @staticmethod + def pairwise_stats(count_a, mean_a, var_a, count_b, mean_b, var_b): + delta = mean_b - mean_a + m_a = var_a * (count_a - 1) + m_b = var_b * (count_b - 1) + M2 = m_a + m_b + delta ** 2 * count_a * count_b / (count_a + count_b) + return ( + count_a + count_b, + mean_a * 0.5 + mean_b * 0.5, + M2 / (count_a + count_b - 1) + ) + + # stats = float array of shape N, 2 * d, d = dimensions of values + # count = integer array of shape N + # mean_accessor = function(idx, stats) to retrieve element mean + # variance_accessor = function(idx, stats) to retrieve element variance + # Recursively computes total count, mean and variance, does O(log(N)) calls + @staticmethod + def compute_stats(stats, counts, mean_accessor, variance_accessor): + m = mean_accessor + v = variance_accessor + n = len(stats) + if n == 1: + return counts[0], m(0, stats), v(0, stats) + if n == 2: + return __class__.pairwise_stats( + counts[0], m(0, stats), v(0, stats), + counts[1], m(1, stats), v(1, stats) + ) + h = n // 2 + return __class__.pairwise_stats( + *__class__.compute_stats(stats[:h], counts[:h], m, v), + *__class__.compute_stats(stats[h:], counts[h:], m, v) + ) + +def compute_image_statistics(dataset): + stats = { + 'dataset': {}, + 'subsets': {} + } + + def _extractor_stats(extractor): + available = True + for item in extractor: + if not (item.has_image and item.image.has_data): + available = False + log.warn("Item %s has no image. Image stats won't be computed", + item.id) + break + + stats = { + 'images count': len(extractor), + } + + if available: + mean, std = mean_std(extractor) + stats.update({ + 'image mean': [float(n) for n in mean[::-1]], + 'image std': [float(n) for n in std[::-1]], + }) + else: + stats.update({ + 'image mean': 'n/a', + 'image std': 'n/a', + }) + return stats + + stats['dataset'].update(_extractor_stats(dataset)) + + subsets = dataset.subsets() or [None] + if subsets and 0 < len([s for s in subsets if s]): + for subset_name in subsets: + stats['subsets'][subset_name] = _extractor_stats( + dataset.get_subset(subset_name)) + + return stats + +def compute_ann_statistics(dataset): + labels = dataset.categories().get(AnnotationType.label) + def get_label(ann): + return labels.items[ann.label].name if ann.label is not None else None + + stats = { + 'images count': len(dataset), + 'annotations count': 0, + 'unannotated images count': 0, + 'unannotated images': [], + 'annotations by type': { t.name: { + 'count': 0, + } for t in AnnotationType }, + 'annotations': {}, + } + by_type = stats['annotations by type'] + + attr_template = { + 'count': 0, + 'values count': 0, + 'values present': set(), + 'distribution': {}, # value -> (count, total%) + } + label_stat = { + 'count': 0, + 'distribution': { l.name: [0, 0] for l in labels.items + }, # label -> (count, total%) + + 'attributes': {}, + } + stats['annotations']['labels'] = label_stat + segm_stat = { + 'avg. area': 0, + 'area distribution': [], # a histogram with 10 bins + # (min, min+10%), ..., (min+90%, max) -> (count, total%) + + 'pixel distribution': { l.name: [0, 0] for l in labels.items + }, # label -> (count, total%) + } + stats['annotations']['segments'] = segm_stat + segm_areas = [] + pixel_dist = segm_stat['pixel distribution'] + total_pixels = 0 + + for item in dataset: + if len(item.annotations) == 0: + stats['unannotated images'].append(item.id) + continue + + for ann in item.annotations: + by_type[ann.type.name]['count'] += 1 + + if not hasattr(ann, 'label') or ann.label is None: + continue + + if ann.type in {AnnotationType.mask, + AnnotationType.polygon, AnnotationType.bbox}: + area = ann.get_area() + segm_areas.append(area) + pixel_dist[get_label(ann)][0] += int(area) + + label_stat['count'] += 1 + label_stat['distribution'][get_label(ann)][0] += 1 + + for name, value in ann.attributes.items(): + if name.lower() in { 'occluded', 'visibility', 'score', + 'id', 'track_id' }: + continue + attrs_stat = label_stat['attributes'].setdefault(name, + deepcopy(attr_template)) + attrs_stat['count'] += 1 + attrs_stat['values present'].add(str(value)) + attrs_stat['distribution'] \ + .setdefault(str(value), [0, 0])[0] += 1 + + stats['annotations count'] = sum(t['count'] for t in + stats['annotations by type'].values()) + stats['unannotated images count'] = len(stats['unannotated images']) + + for label_info in label_stat['distribution'].values(): + label_info[1] = label_info[0] / label_stat['count'] + + for label_attr in label_stat['attributes'].values(): + label_attr['values count'] = len(label_attr['values present']) + label_attr['values present'] = sorted(label_attr['values present']) + for attr_info in label_attr['distribution'].values(): + attr_info[1] = attr_info[0] / label_attr['count'] + + # numpy.sum might be faster, but could overflow with large datasets. + # Python's int can transparently mutate to be of indefinite precision (long) + total_pixels = sum(int(a) for a in segm_areas) + + segm_stat['avg. area'] = total_pixels / (len(segm_areas) or 1.0) + + for label_info in segm_stat['pixel distribution'].values(): + label_info[1] = label_info[0] / total_pixels + + if len(segm_areas) != 0: + hist, bins = np.histogram(segm_areas) + segm_stat['area distribution'] = [{ + 'min': float(bin_min), 'max': float(bin_max), + 'count': int(c), 'percent': int(c) / len(segm_areas) + } for c, (bin_min, bin_max) in zip(hist, zip(bins[:-1], bins[1:]))] + + return stats diff --git a/datumaro/datumaro/components/project.py b/datumaro/datumaro/components/project.py index d4468edd..8ac3ceb0 100644 --- a/datumaro/datumaro/components/project.py +++ b/datumaro/datumaro/components/project.py @@ -18,8 +18,9 @@ import sys from datumaro.components.config import Config, DEFAULT_FORMAT from datumaro.components.config_model import (Model, Source, PROJECT_DEFAULT_CONFIG, PROJECT_SCHEMA) -from datumaro.components.extractor import Extractor -from datumaro.components.launcher import InferenceWrapper +from datumaro.components.extractor import Extractor, LabelCategories,\ + AnnotationType +from datumaro.components.launcher import ModelTransform from datumaro.components.dataset_filter import \ XPathDatasetFilter, XPathAnnotationsFilter @@ -104,7 +105,7 @@ class GitWrapper: def __init__(self, config=None): self.repo = None - if config is not None and osp.isdir(config.project_dir): + if config is not None and config.project_dir: self.init(config.project_dir) @staticmethod @@ -116,8 +117,12 @@ class GitWrapper: spawn = not osp.isdir(cls._git_dir(path)) repo = git.Repo.init(path=path) if spawn: - author = git.Actor("Nobody", "nobody@example.com") - repo.index.commit('Initial commit', author=author) + repo.config_writer().set_value("user", "name", "User") \ + .set_value("user", "email", "user@nowhere.com") \ + .release() + # gitpython does not support init, use git directly + repo.git.init() + repo.git.commit('-m', 'Initial commit', '--allow-empty') return repo def init(self, path): @@ -235,7 +240,17 @@ class Environment: exports = cls._import_module(module_dir, module_name, types, package) except Exception as e: - log.debug("Failed to import module '%s': %s" % (module_name, e)) + module_search_error = ImportError + try: + module_search_error = ModuleNotFoundError # python 3.6+ + except NameError: + pass + + message = ["Failed to import module '%s': %s", module_name, e] + if isinstance(e, module_search_error): + log.debug(*message) + else: + log.warning(*message) continue log.debug("Imported the following symbols from %s: %s" % \ @@ -305,18 +320,38 @@ class Subset(Extractor): return self._parent.categories() class Dataset(Extractor): + @classmethod + def from_iterable(cls, iterable, categories=None): + """Generation of Dataset from iterable object + + Args: + iterable: Iterable object contains DatasetItems + categories (dict, optional): You can pass dict of categories or + you can pass list of names. It'll interpreted as list of names of + LabelCategories. Defaults to {}. + + Returns: + Dataset: Dataset object + """ + + if isinstance(categories, list): + categories = {AnnotationType.label : LabelCategories.from_iterable(categories)} + + if not categories: + categories = {} + + class tmpExtractor(Extractor): + def __iter__(self): + return iter(iterable) + + def categories(self): + return categories + + return cls.from_extractors(tmpExtractor()) + @classmethod def from_extractors(cls, *sources): - # merge categories - # TODO: implement properly with merging and annotations remapping - categories = {} - for source in sources: - categories.update(source.categories()) - for source in sources: - for cat_type, source_cat in source.categories().items(): - if not categories[cat_type] == source_cat: - raise NotImplementedError( - "Merging different categories is not implemented yet") + categories = cls._merge_categories(s.categories() for s in sources) dataset = Dataset(categories=categories) # merge items @@ -367,9 +402,10 @@ class Dataset(Extractor): def get(self, item_id, subset=None, path=None): if path: raise KeyError("Requested dataset item path is not found") - if subset is None: - subset = '' - return self._subsets[subset].items[item_id] + item_id = str(item_id) + subset = subset or '' + subset = self._subsets[subset] + return subset.items[item_id] def put(self, item, item_id=None, subset=None, path=None): if path: @@ -412,7 +448,7 @@ class Dataset(Extractor): @classmethod def _merge_items(cls, existing_item, current_item, path=None): return existing_item.wrap(path=path, - image=cls._merge_images(existing_item, current_item), + image=cls._merge_images(existing_item, current_item), annotations=cls._merge_anno( existing_item.annotations, current_item.annotations)) @@ -444,18 +480,15 @@ class Dataset(Extractor): @staticmethod def _merge_anno(a, b): - from itertools import chain - merged = [] - for item in chain(a, b): - found = False - for elem in merged: - if elem == item: - found = True - break - if not found: - merged.append(item) - - return merged + # TODO: implement properly with merging and annotations remapping + from .operations import merge_annotations_equal + return merge_annotations_equal(a, b) + + @staticmethod + def _merge_categories(sources): + # TODO: implement properly with merging and annotations remapping + from .operations import merge_categories + return merge_categories(sources) class ProjectDataset(Dataset): def __init__(self, project): @@ -490,14 +523,9 @@ class ProjectDataset(Dataset): # merge categories # TODO: implement properly with merging and annotations remapping - categories = {} - for source in self._sources.values(): - categories.update(source.categories()) - for source in self._sources.values(): - for cat_type, source_cat in source.categories().items(): - if not categories[cat_type] == source_cat: - raise NotImplementedError( - "Merging different categories is not implemented yet") + categories = self._merge_categories(s.categories() + for s in self._sources.values()) + # ovewrite with own categories if own_source is not None and (not categories or len(own_source) != 0): categories.update(own_source.categories()) self._categories = categories @@ -557,7 +585,7 @@ class ProjectDataset(Dataset): rest_path = path[1:] return self._sources[source].get( item_id=item_id, subset=subset, path=rest_path) - return self._subsets[subset].items[item_id] + return super().get(item_id, subset) def put(self, item, item_id=None, subset=None, path=None): if path is None: @@ -596,33 +624,37 @@ class ProjectDataset(Dataset): project.config.remove('sources') save_dir = osp.abspath(save_dir) - os.makedirs(save_dir, exist_ok=True) - dataset_save_dir = osp.join(save_dir, project.config.dataset_dir) - os.makedirs(dataset_save_dir, exist_ok=True) converter_kwargs = { 'save_images': save_images, } - if merge: - # merge and save the resulting dataset - converter = self.env.make_converter( - DEFAULT_FORMAT, **converter_kwargs) - converter(self, dataset_save_dir) - else: - if recursive: - # children items should already be updated - # so we just save them recursively - for source in self._sources.values(): - if isinstance(source, ProjectDataset): - source.save(**converter_kwargs) - - converter = self.env.make_converter( - DEFAULT_FORMAT, **converter_kwargs) - converter(self.iterate_own(), dataset_save_dir) + save_dir_existed = osp.exists(save_dir) + try: + os.makedirs(save_dir, exist_ok=True) + os.makedirs(dataset_save_dir, exist_ok=True) - project.save(save_dir) + if merge: + # merge and save the resulting dataset + self.env.converters.get(DEFAULT_FORMAT).convert( + self, dataset_save_dir, **converter_kwargs) + else: + if recursive: + # children items should already be updated + # so we just save them recursively + for source in self._sources.values(): + if isinstance(source, ProjectDataset): + source.save(**converter_kwargs) + + self.env.converters.get(DEFAULT_FORMAT).convert( + self.iterate_own(), dataset_save_dir, **converter_kwargs) + + project.save(save_dir) + except BaseException: + if not save_dir_existed and osp.isdir(save_dir): + shutil.rmtree(save_dir, ignore_errors=True) + raise @property def env(self): @@ -673,7 +705,7 @@ class ProjectDataset(Dataset): if isinstance(model, str): launcher = self._project.make_executable_model(model) - self.transform_project(InferenceWrapper, launcher=launcher, + self.transform_project(ModelTransform, launcher=launcher, save_dir=save_dir, batch_size=batch_size) def export_project(self, save_dir, converter, @@ -690,7 +722,7 @@ class ProjectDataset(Dataset): try: os.makedirs(save_dir, exist_ok=True) converter(dataset, save_dir) - except Exception: + except BaseException: if not save_dir_existed: shutil.rmtree(save_dir) raise @@ -735,7 +767,7 @@ class Project: config_path = osp.join(save_dir, config.project_filename) config.dump(config_path) - except Exception: + except BaseException: if not env_dir_existed: shutil.rmtree(save_dir, ignore_errors=True) if not project_dir_existed: @@ -744,9 +776,10 @@ class Project: @staticmethod def generate(save_dir, config=None): + config = Config(config) + config.project_dir = save_dir project = Project(config) project.save(save_dir) - project.config.project_dir = save_dir return project @staticmethod @@ -807,9 +840,8 @@ class Project: def make_executable_model(self, name): model = self.get_model(name) - model.model_dir = self.local_model_dir(name) return self.env.make_launcher(model.launcher, - **model.options, model_dir=model.model_dir) + **model.options, model_dir=self.local_model_dir(name)) def make_source_project(self, name): source = self.get_source(name) diff --git a/datumaro/datumaro/plugins/accuracy_checker_plugin/__init__.py b/datumaro/datumaro/plugins/accuracy_checker_plugin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/datumaro/datumaro/plugins/accuracy_checker_plugin/details/ac.py b/datumaro/datumaro/plugins/accuracy_checker_plugin/details/ac.py new file mode 100644 index 00000000..4fc2ffb5 --- /dev/null +++ b/datumaro/datumaro/plugins/accuracy_checker_plugin/details/ac.py @@ -0,0 +1,116 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from datumaro.util.tf_util import import_tf +import_tf() # prevent TF loading and potential interpeter crash + +from itertools import groupby + +from accuracy_checker.adapters import create_adapter +from accuracy_checker.data_readers import DataRepresentation +from accuracy_checker.launcher import InputFeeder, create_launcher +from accuracy_checker.postprocessor import PostprocessingExecutor +from accuracy_checker.preprocessor import PreprocessingExecutor +from accuracy_checker.utils import extract_image_representations + +from datumaro.components.extractor import AnnotationType, LabelCategories + +from .representation import import_predictions + + +class _FakeDataset: + def __init__(self, metadata=None): + self.metadata = metadata or {} + +class GenericAcLauncher: + @staticmethod + def from_config(config): + launcher_config = config['launcher'] + launcher = create_launcher(launcher_config) + + dataset = _FakeDataset() + adapter_config = config.get('adapter') or launcher_config.get('adapter') + label_config = adapter_config.get('labels') \ + if isinstance(adapter_config, dict) else None + if label_config: + assert isinstance(label_config, (list, dict)) + if isinstance(label_config, list): + label_config = dict(enumerate(label_config)) + + dataset.metadata = {'label_map': { + int(key): label for key, label in label_config.items() + }} + adapter = create_adapter(adapter_config, launcher, dataset) + + preproc_config = config.get('preprocessing') + preproc = None + if preproc_config: + preproc = PreprocessingExecutor(preproc_config, + dataset_meta=dataset.metadata, + input_shapes=launcher.inputs_info_for_meta() + ) + + postproc_config = config.get('postprocessing') + postproc = None + if postproc_config: + postproc = PostprocessingExecutor(postproc_config, + dataset_meta=dataset.metadata, + ) + + return __class__(launcher, + adapter=adapter, preproc=preproc, postproc=postproc) + + def __init__(self, launcher, adapter=None, + preproc=None, postproc=None, input_feeder=None): + self._launcher = launcher + self._input_feeder = input_feeder or InputFeeder( + launcher.config.get('inputs', []), launcher.inputs, + launcher.fit_to_input, launcher.default_layout + ) + self._adapter = adapter + self._preproc = preproc + self._postproc = postproc + + self._categories = self._init_categories() + + def launch_raw(self, inputs): + ids = range(len(inputs)) + inputs = [DataRepresentation(inp, identifier=id) + for id, inp in zip(ids, inputs)] + _, batch_meta = extract_image_representations(inputs) + + if self._preproc: + inputs = self._preproc.process(inputs) + + inputs = self._input_feeder.fill_inputs(inputs) + outputs = self._launcher.predict(inputs, batch_meta) + + if self._adapter: + outputs = self._adapter.process(outputs, ids, batch_meta) + + if self._postproc: + outputs = self._postproc.process(outputs) + + return outputs + + def launch(self, inputs): + outputs = self.launch_raw(inputs) + return [import_predictions(g) for _, g in + groupby(outputs, key=lambda o: o.identifier)] + + def categories(self): + return self._categories + + def _init_categories(self): + if self._adapter is None or self._adapter.label_map is None: + return None + + label_map = sorted(self._adapter.label_map.items(), key=lambda e: e[0]) + + label_cat = LabelCategories() + for _, label in label_map: + label_cat.add(label) + + return { AnnotationType.label: label_cat } diff --git a/datumaro/datumaro/plugins/accuracy_checker_plugin/details/representation.py b/datumaro/datumaro/plugins/accuracy_checker_plugin/details/representation.py new file mode 100644 index 00000000..d7007806 --- /dev/null +++ b/datumaro/datumaro/plugins/accuracy_checker_plugin/details/representation.py @@ -0,0 +1,62 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from datumaro.util.tf_util import import_tf +import_tf() # prevent TF loading and potential interpeter crash + +import accuracy_checker.representation as ac + +import datumaro.components.extractor as dm +from datumaro.util.annotation_util import softmax + +def import_predictions(predictions): + # Convert Accuracy checker predictions to Datumaro annotations + + anns = [] + + for pred in predictions: + anns.extend(import_prediction(pred)) + + return anns + +def import_prediction(pred): + if isinstance(pred, ac.ClassificationPrediction): + scores = softmax(pred.scores) + return (dm.Label(label_id, attributes={'score': float(score)}) + for label_id, score in enumerate(scores)) + elif isinstance(pred, ac.ArgMaxClassificationPrediction): + return (dm.Label(int(pred.label)), ) + elif isinstance(pred, ac.CharacterRecognitionPrediction): + return (dm.Label(int(pred.label)), ) + elif isinstance(pred, (ac.DetectionPrediction, ac.ActionDetectionPrediction)): + return (dm.Bbox(x0, y0, x1 - x0, y1 - y0, int(label_id), + attributes={'score': float(score)}) + for label, score, x0, y0, x1, y1 in zip(pred.labels, pred.scores, + pred.x_mins, pred.y_mins, pred.x_maxs, pred.y_maxs) + ) + elif isinstance(pred, ac.DepthEstimationPrediction): + return (dm.Mask(pred.depth_map), ) # 2d floating point mask + # elif isinstance(pred, ac.HitRatioPrediction): + # - + elif isinstance(pred, ac.ImageInpaintingPrediction): + return (dm.Mask(pred.value), ) # an image + # elif isinstance(pred, ac.MultiLabelRecognitionPrediction): + # - + # elif isinstance(pred, ac.MachineTranslationPrediction): + # - + # elif isinstance(pred, ac.QuestionAnsweringPrediction): + # - + # elif isinstance(pred, ac.PoseEstimation3dPrediction): + # - + # elif isinstance(pred, ac.PoseEstimationPrediction): + # - + # elif isinstance(pred, ac.RegressionPrediction): + # - + else: + raise NotImplementedError("Can't convert %s" % type(pred)) + + + + diff --git a/datumaro/datumaro/plugins/accuracy_checker_plugin/launcher.py b/datumaro/datumaro/plugins/accuracy_checker_plugin/launcher.py new file mode 100644 index 00000000..15251108 --- /dev/null +++ b/datumaro/datumaro/plugins/accuracy_checker_plugin/launcher.py @@ -0,0 +1,37 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os.path as osp +import yaml + +from datumaro.components.cli_plugin import CliPlugin +from datumaro.components.launcher import Launcher + +from .details.ac import GenericAcLauncher as _GenericAcLauncher + + +class AcLauncher(Launcher, CliPlugin): + """ + Generic model launcher with Accuracy Checker backend. + """ + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-c', '--config', type=osp.abspath, required=True, + help="Path to the launcher configuration file (.yml)") + return parser + + def __init__(self, config, model_dir=None): + model_dir = model_dir or '' + with open(osp.join(model_dir, config), 'r') as f: + config = yaml.safe_load(f) + self._launcher = _GenericAcLauncher.from_config(config) + + def launch(self, inputs): + return self._launcher.launch(inputs) + + def categories(self): + return self._launcher.categories() diff --git a/datumaro/datumaro/plugins/coco_format/converter.py b/datumaro/datumaro/plugins/coco_format/converter.py index 9d1d7289..27cdd087 100644 --- a/datumaro/datumaro/plugins/coco_format/converter.py +++ b/datumaro/datumaro/plugins/coco_format/converter.py @@ -3,27 +3,23 @@ # # SPDX-License-Identifier: MIT -from enum import Enum -from itertools import groupby import json import logging as log import os import os.path as osp +from enum import Enum +from itertools import groupby import pycocotools.mask as mask_utils -from datumaro.components.converter import Converter -from datumaro.components.extractor import (DEFAULT_SUBSET_NAME, - AnnotationType, Points -) -from datumaro.components.cli_plugin import CliPlugin -from datumaro.util import find, cast -from datumaro.util.image import save_image +import datumaro.util.annotation_util as anno_tools import datumaro.util.mask_tools as mask_tools -import datumaro.util.annotation_tools as anno_tools - -from .format import CocoTask, CocoPath +from datumaro.components.converter import Converter +from datumaro.components.extractor import (_COORDINATE_ROUNDING_DIGITS, + DEFAULT_SUBSET_NAME, AnnotationType, Points) +from datumaro.util import cast, find, str_to_bool +from .format import CocoPath, CocoTask SegmentationMode = Enum('SegmentationMode', ['guess', 'polygons', 'mask']) @@ -110,6 +106,12 @@ class _TaskConverter: self._min_ann_id = max(ann_id, self._min_ann_id) return ann_id + @staticmethod + def _convert_attributes(ann): + return { k: v for k, v in ann.attributes.items() + if k not in {'is_crowd', 'score'} + } + class _ImageInfoConverter(_TaskConverter): def is_empty(self): return len(self._data['images']) == 0 @@ -141,6 +143,8 @@ class _CaptionsConverter(_TaskConverter): except Exception as e: log.warning("Item '%s', ann #%s: failed to convert " "attribute 'score': %e" % (item.id, ann_idx, e)) + if self._context._allow_attributes: + elem['attributes'] = self._convert_attributes(ann) self.annotations.append(elem) @@ -198,7 +202,7 @@ class _InstancesConverter(_TaskConverter): anns = boxes + polygons + masks leader = anno_tools.find_group_leader(anns) - bbox = anno_tools.compute_bbox(anns) + bbox = anno_tools.max_bbox(anns) mask = None polygons = [p.points for p in polygons] @@ -293,8 +297,8 @@ class _InstancesConverter(_TaskConverter): rles = mask_utils.merge(rles) area = mask_utils.area(rles) else: - x, y, w, h = bbox - segmentation = [[x, y, x + w, y, x + w, y + h, x, y + h]] + _, _, w, h = bbox + segmentation = [] area = w * h elem = { @@ -303,7 +307,7 @@ class _InstancesConverter(_TaskConverter): 'category_id': cast(ann.label, int, -1) + 1, 'segmentation': segmentation, 'area': float(area), - 'bbox': list(map(float, bbox)), + 'bbox': [round(float(n), _COORDINATE_ROUNDING_DIGITS) for n in bbox], 'iscrowd': int(is_crowd), } if 'score' in ann.attributes: @@ -312,6 +316,8 @@ class _InstancesConverter(_TaskConverter): except Exception as e: log.warning("Item '%s': failed to convert attribute " "'score': %e" % (item.id, e)) + if self._context._allow_attributes: + elem['attributes'] = self._convert_attributes(ann) return elem @@ -329,7 +335,6 @@ class _KeypointsConverter(_InstancesConverter): 'supercategory': cast(label_cat.parent, str, ''), 'keypoints': [], 'skeleton': [], - } if point_categories is not None: @@ -428,10 +433,45 @@ class _LabelsConverter(_TaskConverter): except Exception as e: log.warning("Item '%s': failed to convert attribute " "'score': %e" % (item.id, e)) + if self._context._allow_attributes: + elem['attributes'] = self._convert_attributes(ann) self.annotations.append(elem) -class _Converter: +class CocoConverter(Converter): + @staticmethod + def _split_tasks_string(s): + return [CocoTask[i.strip()] for i in s.split(',')] + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('--segmentation-mode', + choices=[m.name for m in SegmentationMode], + default=SegmentationMode.guess.name, + help=""" + Save mode for instance segmentation:|n + - '{sm.guess.name}': guess the mode for each instance,|n + |s|suse 'is_crowd' attribute as hint|n + - '{sm.polygons.name}': save polygons,|n + |s|smerge and convert masks, prefer polygons|n + - '{sm.mask.name}': save masks,|n + |s|smerge and convert polygons, prefer masks|n + Default: %(default)s. + """.format(sm=SegmentationMode)) + parser.add_argument('--crop-covered', action='store_true', + help="Crop covered segments so that background objects' " + "segmentation was more accurate (default: %(default)s)") + parser.add_argument('--allow-attributes', + type=str_to_bool, default=True, + help="Allow export of attributes (default: %(default)s)") + parser.add_argument('--tasks', type=cls._split_tasks_string, + help="COCO task filter, comma-separated list of {%s} " + "(default: all)" % ', '.join(t.name for t in CocoTask)) + return parser + + DEFAULT_IMAGE_EXT = CocoPath.IMAGE_EXT + _TASK_CONVERTER = { CocoTask.image_info: _ImageInfoConverter, CocoTask.instances: _InstancesConverter, @@ -441,16 +481,16 @@ class _Converter: } def __init__(self, extractor, save_dir, - tasks=None, save_images=False, segmentation_mode=None, - crop_covered=False): + tasks=None, segmentation_mode=None, crop_covered=False, + allow_attributes=True, **kwargs): + super().__init__(extractor, save_dir, **kwargs) + assert tasks is None or isinstance(tasks, (CocoTask, list, str)) - if tasks is None: - tasks = list(self._TASK_CONVERTER) - elif isinstance(tasks, CocoTask): + if isinstance(tasks, CocoTask): tasks = [tasks] elif isinstance(tasks, str): tasks = [CocoTask[tasks]] - else: + elif tasks: for i, t in enumerate(tasks): if isinstance(t, str): tasks[i] = CocoTask[t] @@ -458,11 +498,6 @@ class _Converter: assert t in CocoTask, t self._tasks = tasks - self._extractor = extractor - self._save_dir = save_dir - - self._save_images = save_images - assert segmentation_mode is None or \ isinstance(segmentation_mode, str) or \ segmentation_mode in SegmentationMode @@ -473,6 +508,7 @@ class _Converter: self._segmentation_mode = segmentation_mode self._crop_covered = crop_covered + self._allow_attributes = allow_attributes self._image_ids = {} @@ -489,41 +525,25 @@ class _Converter: return self._TASK_CONVERTER[task](self) def _make_task_converters(self): - return { - task: self._make_task_converter(task) for task in self._tasks - } + return { task: self._make_task_converter(task) + for task in (self._tasks or self._TASK_CONVERTER) } def _get_image_id(self, item): image_id = self._image_ids.get(item.id) if image_id is None: - image_id = cast(item.id, int, len(self._image_ids) + 1) + image_id = cast(item.attributes.get('id'), int, + len(self._image_ids) + 1) self._image_ids[item.id] = image_id return image_id - def _save_image(self, item): - image = item.image.data - if image is None: - log.warning("Item '%s' has no image" % item.id) - return '' + def _save_image(self, item, path=None): + super()._save_image(item, + osp.join(self._images_dir, self._make_image_filename(item))) - filename = item.image.filename - if filename: - filename = osp.splitext(filename)[0] - else: - filename = item.id - filename += CocoPath.IMAGE_EXT - path = osp.join(self._images_dir, filename) - save_image(path, image) - return path - - def convert(self): + def apply(self): self._make_dirs() - subsets = self._extractor.subsets() - if len(subsets) == 0: - subsets = [ None ] - - for subset_name in subsets: + for subset_name in self._extractor.subsets() or [None]: if subset_name: subset = self._extractor.get_subset(subset_name) else: @@ -534,91 +554,43 @@ class _Converter: for task_conv in task_converters.values(): task_conv.save_categories(subset) for item in subset: - filename = '' - if item.has_image: - filename = item.image.path if self._save_images: if item.has_image: - filename = self._save_image(item) + self._save_image(item) else: - log.debug("Item '%s' has no image info" % item.id) + log.debug("Item '%s' has no image info", item.id) for task_conv in task_converters.values(): - task_conv.save_image_info(item, filename) + task_conv.save_image_info(item, + self._make_image_filename(item)) task_conv.save_annotations(item) for task, task_conv in task_converters.items(): + if task_conv.is_empty() and not self._tasks: + continue task_conv.write(osp.join(self._ann_dir, '%s_%s.json' % (task.name, subset_name))) -class CocoConverter(Converter, CliPlugin): - @staticmethod - def _split_tasks_string(s): - return [CocoTask[i.strip()] for i in s.split(',')] - - @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().build_cmdline_parser(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - parser.add_argument('--segmentation-mode', - choices=[m.name for m in SegmentationMode], - default=SegmentationMode.guess.name, - help=""" - Save mode for instance segmentation:|n - - '{sm.guess.name}': guess the mode for each instance,|n - |s|suse 'is_crowd' attribute as hint|n - - '{sm.polygons.name}': save polygons,|n - |s|smerge and convert masks, prefer polygons|n - - '{sm.mask.name}': save masks,|n - |s|smerge and convert polygons, prefer masks|n - Default: %(default)s. - """.format(sm=SegmentationMode)) - parser.add_argument('--crop-covered', action='store_true', - help="Crop covered segments so that background objects' " - "segmentation was more accurate (default: %(default)s)") - parser.add_argument('--tasks', type=cls._split_tasks_string, - default=None, - help="COCO task filter, comma-separated list of {%s} " - "(default: all)" % ', '.join([t.name for t in CocoTask])) - return parser - - def __init__(self, - tasks=None, save_images=False, segmentation_mode=None, - crop_covered=False): - super().__init__() - - self._options = { - 'tasks': tasks, - 'save_images': save_images, - 'segmentation_mode': segmentation_mode, - 'crop_covered': crop_covered, - } - - def __call__(self, extractor, save_dir): - converter = _Converter(extractor, save_dir, **self._options) - converter.convert() - class CocoInstancesConverter(CocoConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = CocoTask.instances - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class CocoImageInfoConverter(CocoConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = CocoTask.image_info - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class CocoPersonKeypointsConverter(CocoConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = CocoTask.person_keypoints - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class CocoCaptionsConverter(CocoConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = CocoTask.captions - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class CocoLabelsConverter(CocoConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = CocoTask.labels - super().__init__(**kwargs) + super().__init__(*args, **kwargs) diff --git a/datumaro/datumaro/plugins/coco_format/extractor.py b/datumaro/datumaro/plugins/coco_format/extractor.py index a4f52f81..8bb6e464 100644 --- a/datumaro/datumaro/plugins/coco_format/extractor.py +++ b/datumaro/datumaro/plugins/coco_format/extractor.py @@ -24,7 +24,8 @@ class _CocoExtractor(SourceExtractor): def __init__(self, path, task, merge_instance_polygons=False): assert osp.isfile(path), path - subset = osp.splitext(osp.basename(path))[0].rsplit('_', maxsplit=1)[1] + subset = osp.splitext(osp.basename(path))[0].rsplit('_', maxsplit=1) + subset = subset[1] if len(subset) == 2 else None super().__init__(subset=subset) rootpath = '' @@ -125,8 +126,10 @@ class _CocoExtractor(SourceExtractor): anns = loader.loadAnns(anns) anns = sum((self._load_annotations(a, image_info) for a in anns), []) - items[img_id] = DatasetItem(id=img_id, subset=self._subset, - image=image, annotations=anns) + items[img_id] = DatasetItem( + id=osp.splitext(image_info['file_name'])[0], + subset=self._subset, image=image, annotations=anns, + attributes={'id': img_id}) return items @@ -142,6 +145,12 @@ class _CocoExtractor(SourceExtractor): ann_id = ann.get('id') attributes = {} + if 'attributes' in ann: + try: + attributes.update(ann['attributes']) + except Exception as e: + log.debug("item #%s: failed to read annotation attributes: %s", + image_info['id'], e) if 'score' in ann: attributes['score'] = ann['score'] diff --git a/datumaro/datumaro/plugins/cvat_format/converter.py b/datumaro/datumaro/plugins/cvat_format/converter.py index c04eef49..37751703 100644 --- a/datumaro/datumaro/plugins/cvat_format/converter.py +++ b/datumaro/datumaro/plugins/cvat_format/converter.py @@ -3,25 +3,19 @@ # # SPDX-License-Identifier: MIT -from collections import OrderedDict import logging as log import os import os.path as osp +from collections import OrderedDict from xml.sax.saxutils import XMLGenerator -from datumaro.components.cli_plugin import CliPlugin from datumaro.components.converter import Converter from datumaro.components.extractor import DEFAULT_SUBSET_NAME, AnnotationType -from datumaro.util import cast -from datumaro.util.image import save_image +from datumaro.util import cast, pairs from .format import CvatPath -def pairwise(iterable): - a = iter(iterable) - return zip(a, a) - class XmlAnnotationWriter: VERSION = '1.1' @@ -163,26 +157,12 @@ class _SubsetWriter: self._writer.close_root() - def _save_image(self, item): - image = item.image.data - if image is None: - log.warning("Item '%s' has no image" % item.id) - return '' - - filename = item.image.filename - if filename: - filename = osp.splitext(filename)[0] - else: - filename = item.id - filename += CvatPath.IMAGE_EXT - image_path = osp.join(self._context._images_dir, filename) - save_image(image_path, image) - return filename - def _write_item(self, item, index): image_info = OrderedDict([ - ("id", str(cast(item.id, int, index))), + ("id", str(cast(item.attributes.get('frame'), int, index))), ]) + filename = item.id + CvatPath.IMAGE_EXT + image_info["name"] = filename if item.has_image: size = item.image.size if size: @@ -190,12 +170,11 @@ class _SubsetWriter: image_info["width"] = str(w) image_info["height"] = str(h) - filename = item.image.filename if self._context._save_images: - filename = self._save_image(item) - image_info["name"] = filename + self._context._save_image(item, + osp.join(self._context._images_dir, filename)) else: - log.debug("Item '%s' has no image info" % item.id) + log.debug("Item '%s' has no image info", item.id) self._writer.open_image(image_info) for ann in item.annotations: @@ -267,7 +246,7 @@ class _SubsetWriter: ','.join(( "{:.2f}".format(x), "{:.2f}".format(y) - )) for x, y in pairwise(shape.points)) + )) for x, y in pairs(shape.points)) )), ])) @@ -328,23 +307,14 @@ class _SubsetWriter: self._writer.close_tag() -class _Converter: - def __init__(self, extractor, save_dir, save_images=False): - self._extractor = extractor - self._save_dir = save_dir - self._save_images = save_images - - def convert(self): - os.makedirs(self._save_dir, exist_ok=True) +class CvatConverter(Converter): + DEFAULT_IMAGE_EXT = CvatPath.IMAGE_EXT + def apply(self): images_dir = osp.join(self._save_dir, CvatPath.IMAGES_DIR) os.makedirs(images_dir, exist_ok=True) self._images_dir = images_dir - annotations_dir = osp.join(self._save_dir, CvatPath.ANNOTATIONS_DIR) - os.makedirs(annotations_dir, exist_ok=True) - self._annotations_dir = annotations_dir - subsets = self._extractor.subsets() if len(subsets) == 0: subsets = [ None ] @@ -356,25 +326,6 @@ class _Converter: subset_name = DEFAULT_SUBSET_NAME subset = self._extractor - with open(osp.join(annotations_dir, '%s.xml' % subset_name), 'w') as f: + with open(osp.join(self._save_dir, '%s.xml' % subset_name), 'w') as f: writer = _SubsetWriter(f, subset_name, subset, self) writer.write() - -class CvatConverter(Converter, CliPlugin): - @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().build_cmdline_parser(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - return parser - - def __init__(self, save_images=False): - super().__init__() - - self._options = { - 'save_images': save_images, - } - - def __call__(self, extractor, save_dir): - converter = _Converter(extractor, save_dir, **self._options) - converter.convert() \ No newline at end of file diff --git a/datumaro/datumaro/plugins/cvat_format/extractor.py b/datumaro/datumaro/plugins/cvat_format/extractor.py index c268b31d..75a3e5d8 100644 --- a/datumaro/datumaro/plugins/cvat_format/extractor.py +++ b/datumaro/datumaro/plugins/cvat_format/extractor.py @@ -21,11 +21,9 @@ class CvatExtractor(SourceExtractor): def __init__(self, path): assert osp.isfile(path), path - rootpath = '' - if path.endswith(osp.join(CvatPath.ANNOTATIONS_DIR, osp.basename(path))): - rootpath = path.rsplit(CvatPath.ANNOTATIONS_DIR, maxsplit=1)[0] + rootpath = osp.dirname(path) images_dir = '' - if rootpath and osp.isdir(osp.join(rootpath, CvatPath.IMAGES_DIR)): + if osp.isdir(osp.join(rootpath, CvatPath.IMAGES_DIR)): images_dir = osp.join(rootpath, CvatPath.IMAGES_DIR) self._images_dir = images_dir self._path = path @@ -64,7 +62,7 @@ class CvatExtractor(SourceExtractor): if ev == 'start': if el.tag == 'track': track = { - 'id': el.attrib.get('id'), + 'id': el.attrib['id'], 'label': el.attrib.get('label'), 'group': int(el.attrib.get('group_id', 0)), 'height': frame_size[0], @@ -85,6 +83,7 @@ class CvatExtractor(SourceExtractor): } if track: shape.update(track) + shape['track_id'] = int(track['id']) if image: shape.update(image) elif el.tag == 'tag' and image: @@ -103,7 +102,7 @@ class CvatExtractor(SourceExtractor): else: try: attr_value = float(attr_value) - except Exception: + except ValueError: pass attributes[el.attrib['name']] = attr_value elif el.tag in cls._SUPPORTED_SHAPES: @@ -165,8 +164,7 @@ class CvatExtractor(SourceExtractor): categories = {} frame_size = None - has_z_order = False - mode = 'annotation' + mode = None labels = OrderedDict() label = None @@ -192,7 +190,7 @@ class CvatExtractor(SourceExtractor): if ev == 'start': if accepted('annotations', 'meta'): pass elif accepted('meta', 'task'): pass - elif accepted('task', 'z_order'): pass + elif accepted('task', 'mode'): pass elif accepted('task', 'original_size'): frame_size = [None, None] elif accepted('original_size', 'height', next_state='frame_height'): pass @@ -214,8 +212,8 @@ class CvatExtractor(SourceExtractor): if consumed('meta', 'meta'): break elif consumed('task', 'task'): pass - elif consumed('z_order', 'z_order'): - has_z_order = (el.text == 'True') + elif consumed('mode', 'mode'): + mode = el.text elif consumed('original_size', 'original_size'): pass elif consumed('frame_height', 'height'): frame_size[0] = int(el.text) @@ -241,6 +239,7 @@ class CvatExtractor(SourceExtractor): if mode == 'interpolation': common_attrs.append('keyframe') common_attrs.append('outside') + common_attrs.append('track_id') label_cat = LabelCategories(attributes=common_attrs) for label, attrs in labels.items(): @@ -252,16 +251,18 @@ class CvatExtractor(SourceExtractor): @classmethod def _parse_shape_ann(cls, ann, categories): - ann_id = ann.get('id') + ann_id = ann.get('id', 0) ann_type = ann['type'] - attributes = ann.get('attributes', {}) + attributes = ann.get('attributes') or {} if 'occluded' in categories[AnnotationType.label].attributes: attributes['occluded'] = ann.get('occluded', False) - if 'outside' in categories[AnnotationType.label].attributes: - attributes['outside'] = ann.get('outside', False) - if 'keyframe' in categories[AnnotationType.label].attributes: - attributes['keyframe'] = ann.get('keyframe', False) + if 'outside' in ann: + attributes['outside'] = ann['outside'] + if 'keyframe' in ann: + attributes['keyframe'] = ann['keyframe'] + if 'track_id' in ann: + attributes['track_id'] = ann['track_id'] group = ann.get('group') @@ -302,30 +303,14 @@ class CvatExtractor(SourceExtractor): def _load_items(self, parsed): for frame_id, item_desc in parsed.items(): - filename = item_desc.get('name') - if filename: - filename = self._find_image(filename) - if not filename: - filename = item_desc.get('name') + name = item_desc.get('name', 'frame_%06d.png' % int(frame_id)) + image = osp.join(self._images_dir, name) image_size = (item_desc.get('height'), item_desc.get('width')) if all(image_size): - image_size = (int(image_size[0]), int(image_size[1])) - else: - image_size = None - image = None - if filename: - image = Image(path=filename, size=image_size) - - parsed[frame_id] = DatasetItem(id=frame_id, subset=self._subset, - image=image, annotations=item_desc.get('annotations')) - return parsed + image = Image(path=image, size=tuple(map(int, image_size))) - def _find_image(self, file_name): - search_paths = [] - if self._images_dir: - search_paths += [ osp.join(self._images_dir, file_name) ] - search_paths += [ osp.join(osp.dirname(self._path), file_name) ] - for image_path in search_paths: - if osp.isfile(image_path): - return image_path - return None + parsed[frame_id] = DatasetItem(id=osp.splitext(name)[0], + subset=self._subset, image=image, + annotations=item_desc.get('annotations'), + attributes={'frame': int(frame_id)}) + return parsed diff --git a/datumaro/datumaro/plugins/cvat_format/format.py b/datumaro/datumaro/plugins/cvat_format/format.py index e0c7a104..c73fd467 100644 --- a/datumaro/datumaro/plugins/cvat_format/format.py +++ b/datumaro/datumaro/plugins/cvat_format/format.py @@ -5,6 +5,5 @@ class CvatPath: IMAGES_DIR = 'images' - ANNOTATIONS_DIR = 'annotations' IMAGE_EXT = '.jpg' diff --git a/datumaro/datumaro/plugins/datumaro_format/converter.py b/datumaro/datumaro/plugins/datumaro_format/converter.py index 1976bb1e..81c2cd55 100644 --- a/datumaro/datumaro/plugins/datumaro_format/converter.py +++ b/datumaro/datumaro/plugins/datumaro_format/converter.py @@ -17,9 +17,7 @@ from datumaro.components.extractor import ( LabelCategories, MaskCategories, PointsCategories ) from datumaro.util import cast -from datumaro.util.image import save_image import pycocotools.mask as mask_utils -from datumaro.components.cli_plugin import CliPlugin from .format import DatumaroPath @@ -49,12 +47,15 @@ class _SubsetWriter: 'id': item.id, 'annotations': annotations, } + if item.attributes: + item_desc['attr'] = item.attributes if item.path: item_desc['path'] = item.path if item.has_image: path = item.image.path if self._context._save_images: - path = self._context._save_image(item) + path = self._context._make_image_filename(item) + self._context._save_image(item, path) item_desc['image'] = { 'size': item.image.size, @@ -211,13 +212,10 @@ class _SubsetWriter: }) return converted -class _Converter: - def __init__(self, extractor, save_dir, save_images=False): - self._extractor = extractor - self._save_dir = save_dir - self._save_images = save_images +class DatumaroConverter(Converter): + DEFAULT_IMAGE_EXT = DatumaroPath.IMAGE_EXT - def convert(self): + def apply(self): os.makedirs(self._save_dir, exist_ok=True) images_dir = osp.join(self._save_dir, DatumaroPath.IMAGES_DIR) @@ -228,19 +226,15 @@ class _Converter: os.makedirs(annotations_dir, exist_ok=True) self._annotations_dir = annotations_dir - subsets = self._extractor.subsets() - if len(subsets) == 0: - subsets = [ None ] - subsets = [n if n else DEFAULT_SUBSET_NAME for n in subsets] + subsets = self._extractor.subsets() or [None] + subsets = [n or DEFAULT_SUBSET_NAME for n in subsets] subsets = { name: _SubsetWriter(name, self) for name in subsets } for subset, writer in subsets.items(): writer.write_categories(self._extractor.categories()) for item in self._extractor: - subset = item.subset - if not subset: - subset = DEFAULT_SUBSET_NAME + subset = item.subset or DEFAULT_SUBSET_NAME writer = subsets[subset] writer.write_item(item) @@ -248,60 +242,20 @@ class _Converter: for subset, writer in subsets.items(): writer.write(annotations_dir) - def _save_image(self, item): - image = item.image.data - if image is None: - return '' - - filename = item.image.filename - if filename: - filename = osp.splitext(filename)[0] - else: - filename = item.id - filename += DatumaroPath.IMAGE_EXT - image_path = osp.join(self._images_dir, filename) - save_image(image_path, image) - return filename - -class DatumaroConverter(Converter, CliPlugin): - @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().build_cmdline_parser(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - return parser - - def __init__(self, save_images=False): - super().__init__() - - self._options = { - 'save_images': save_images, - } - - def __call__(self, extractor, save_dir): - converter = _Converter(extractor, save_dir, **self._options) - converter.convert() - + def _save_image(self, item, path=None): + super()._save_image(item, + osp.join(self._images_dir, self._make_image_filename(item))) class DatumaroProjectConverter(Converter): @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().build_cmdline_parser(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - return parser - - def __init__(self, config=None, save_images=False): - self._config = config - self._save_images = save_images - - def __call__(self, extractor, save_dir): + def convert(cls, extractor, save_dir, **kwargs): os.makedirs(save_dir, exist_ok=True) from datumaro.components.project import Project - project = Project.generate(save_dir, config=self._config) + project = Project.generate(save_dir, + config=kwargs.pop('project_config', None)) - converter = project.env.make_converter('datumaro', - save_images=self._save_images) - converter(extractor, save_dir=osp.join( - project.config.project_dir, project.config.dataset_dir)) \ No newline at end of file + DatumaroConverter.convert(extractor, + save_dir=osp.join( + project.config.project_dir, project.config.dataset_dir), + **kwargs) \ No newline at end of file diff --git a/datumaro/datumaro/plugins/datumaro_format/extractor.py b/datumaro/datumaro/plugins/datumaro_format/extractor.py index 7be72b30..71eb6856 100644 --- a/datumaro/datumaro/plugins/datumaro_format/extractor.py +++ b/datumaro/datumaro/plugins/datumaro_format/extractor.py @@ -82,22 +82,25 @@ class DatumaroExtractor(SourceExtractor): item_id = item_desc['id'] image = None - image_info = item_desc.get('image', {}) + image_info = item_desc.get('image') if image_info: - image_path = osp.join(self._images_dir, - image_info.get('path', '')) # relative or absolute fits + image_path = image_info.get('path') or \ + item_id + DatumaroPath.IMAGE_EXT + image_path = osp.join(self._images_dir, image_path) image = Image(path=image_path, size=image_info.get('size')) annotations = self._load_annotations(item_desc) item = DatasetItem(id=item_id, subset=self._subset, - annotations=annotations, image=image) + annotations=annotations, image=image, + attributes=item_desc.get('attr')) items.append(item) return items - def _load_annotations(self, item): + @staticmethod + def _load_annotations(item): parsed = item['annotations'] loaded = [] diff --git a/datumaro/datumaro/plugins/image_dir.py b/datumaro/datumaro/plugins/image_dir.py index 5f3a1884..410a91f8 100644 --- a/datumaro/datumaro/plugins/image_dir.py +++ b/datumaro/datumaro/plugins/image_dir.py @@ -3,13 +3,13 @@ # # SPDX-License-Identifier: MIT -from collections import OrderedDict +import logging as log import os import os.path as osp from datumaro.components.extractor import DatasetItem, SourceExtractor, Importer from datumaro.components.converter import Converter -from datumaro.util.image import save_image +from datumaro.util.image import Image class ImageDirImporter(Importer): @@ -33,54 +33,44 @@ class ImageDirImporter(Importer): class ImageDirExtractor(SourceExtractor): - _SUPPORTED_FORMATS = ['.png', '.jpg'] - def __init__(self, url): super().__init__() assert osp.isdir(url), url items = [] - for name in os.listdir(url): - path = osp.join(url, name) - if self._is_image(path): - item_id = osp.splitext(name)[0] - item = DatasetItem(id=item_id, image=path) - items.append((item.id, item)) - - items = sorted(items, key=lambda e: e[0]) - items = OrderedDict(items) + for dirpath, _, filenames in os.walk(url): + for name in filenames: + path = osp.join(dirpath, name) + try: + image = Image(path) + # force loading + image.data # pylint: disable=pointless-statement + except Exception: + continue + + item_id = osp.relpath(osp.splitext(path)[0], url) + items.append(DatasetItem(id=item_id, image=image)) + self._items = items def __iter__(self): - for item in self._items.values(): + for item in self._items: yield item def __len__(self): return len(self._items) - def get(self, item_id, subset=None, path=None): - if path or subset: - raise KeyError() - return self._items[item_id] - def _is_image(self, path): - for ext in self._SUPPORTED_FORMATS: - if osp.isfile(path) and path.endswith(ext): - return True - return False +class ImageDirConverter(Converter): + DEFAULT_IMAGE_EXT = '.jpg' + def apply(self): + os.makedirs(self._save_dir, exist_ok=True) -class ImageDirConverter(Converter): - def __call__(self, extractor, save_dir): - os.makedirs(save_dir, exist_ok=True) - - for item in extractor: - if item.has_image and item.image.has_data: - filename = item.image.filename - if filename: - filename = osp.splitext(filename)[0] - else: - filename = item.id - filename += '.jpg' - save_image(osp.join(save_dir, filename), item.image.data) \ No newline at end of file + for item in self._extractor: + if item.has_image: + self._save_image(item, + osp.join(self._save_dir, self._make_image_filename(item))) + else: + log.debug("Item '%s' has no image info", item.id) \ No newline at end of file diff --git a/datumaro/datumaro/plugins/labelme_format.py b/datumaro/datumaro/plugins/labelme_format.py index 0607ba9b..5218e36f 100644 --- a/datumaro/datumaro/plugins/labelme_format.py +++ b/datumaro/datumaro/plugins/labelme_format.py @@ -15,7 +15,6 @@ from datumaro.components.extractor import (SourceExtractor, DEFAULT_SUBSET_NAME, ) from datumaro.components.extractor import Importer from datumaro.components.converter import Converter -from datumaro.components.cli_plugin import CliPlugin from datumaro.util.image import Image, save_image from datumaro.util.mask_tools import load_mask, find_mask_bbox @@ -54,9 +53,7 @@ class LabelMeExtractor(SourceExtractor): for p in sorted(p for p in os.listdir(path) if p.endswith('.xml')): root = ElementTree.parse(osp.join(path, p)) - image = None image_path = osp.join(path, root.find('filename').text) - image_size = None imagesize_elem = root.find('imagesize') if imagesize_elem is not None: @@ -67,8 +64,8 @@ class LabelMeExtractor(SourceExtractor): annotations = self._parse_annotations(root, path, categories) - items.append(DatasetItem(id=osp.splitext(p)[0], subset=self._subset, - image=image, annotations=annotations)) + items.append(DatasetItem(id=osp.splitext(p)[0], + subset=self._subset, image=image, annotations=annotations)) return items, categories @classmethod @@ -86,7 +83,7 @@ class LabelMeExtractor(SourceExtractor): else: try: value = float(value) - except Exception: + except ValueError: pass parsed.append((name, value)) else: @@ -256,8 +253,7 @@ class LabelMeImporter(Importer): params.update(extra_params) source_name = osp.splitext(osp.basename(subset_path))[0] - project.add_source(source_name, - { + project.add_source(source_name, { 'url': subset_path, 'format': self._EXTRACTOR_NAME, 'options': params, @@ -287,34 +283,18 @@ class LabelMeImporter(Importer): return subset_paths -class LabelMeConverter(Converter, CliPlugin): - @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().build_cmdline_parser(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - return parser - - def __init__(self, save_images=False): - super().__init__() - - self._save_images = save_images - - def __call__(self, extractor, save_dir): - self._extractor = extractor +class LabelMeConverter(Converter): + DEFAULT_IMAGE_EXT = LabelMePath.IMAGE_EXT - subsets = extractor.subsets() - if len(subsets) == 0: - subsets = [ None ] - - for subset_name in subsets: + def apply(self): + for subset_name in self._extractor.subsets() or [None]: if subset_name: - subset = extractor.get_subset(subset_name) + subset = self._extractor.get_subset(subset_name) else: subset_name = DEFAULT_SUBSET_NAME - subset = extractor + subset = self._extractor - subset_dir = osp.join(save_dir, subset_name) + subset_dir = osp.join(self._save_dir, subset_name) os.makedirs(subset_dir, exist_ok=True) os.makedirs(osp.join(subset_dir, LabelMePath.MASKS_DIR), exist_ok=True) @@ -333,20 +313,16 @@ class LabelMeConverter(Converter, CliPlugin): log.debug("Converting item '%s'", item.id) - image_filename = '' - if item.has_image: - image_filename = item.image.filename + if '/' in item.id: + raise Exception("Can't export item '%s': " + "LabelMe format only supports flat image layout" % item.id) + + image_filename = self._make_image_filename(item) if self._save_images: if item.has_image and item.image.has_data: - if image_filename: - image_filename = osp.splitext(image_filename)[0] - else: - image_filename = item.id - image_filename += LabelMePath.IMAGE_EXT - save_image(osp.join(subset_dir, image_filename), - item.image.data) + self._save_image(item, osp.join(subset_dir, image_filename)) else: - log.debug("Item '%s' has no image" % item.id) + log.debug("Item '%s' has no image", item.id) root_elem = ET.Element('annotation') ET.SubElement(root_elem, 'filename').text = image_filename diff --git a/datumaro/datumaro/plugins/mot_format.py b/datumaro/datumaro/plugins/mot_format.py index b39c6070..f3776078 100644 --- a/datumaro/datumaro/plugins/mot_format.py +++ b/datumaro/datumaro/plugins/mot_format.py @@ -19,7 +19,6 @@ from datumaro.components.extractor import (SourceExtractor, ) from datumaro.components.extractor import Importer from datumaro.components.converter import Converter -from datumaro.components.cli_plugin import CliPlugin from datumaro.util import cast from datumaro.util.image import Image, save_image @@ -253,25 +252,17 @@ class MotSeqImporter(Importer): subsets.append(p) return subsets -class MotSeqGtConverter(Converter, CliPlugin): - @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().__init__(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - return parser - - def __init__(self, save_images=False): - super().__init__() +class MotSeqGtConverter(Converter): + DEFAULT_IMAGE_EXT = MotPath.IMAGE_EXT - self._save_images = save_images + def apply(self): + extractor = self._extractor - def __call__(self, extractor, save_dir): - images_dir = osp.join(save_dir, MotPath.IMAGE_DIR) + images_dir = osp.join(self._save_dir, MotPath.IMAGE_DIR) os.makedirs(images_dir, exist_ok=True) self._images_dir = images_dir - anno_dir = osp.join(save_dir, 'gt') + anno_dir = osp.join(self._save_dir, 'gt') os.makedirs(anno_dir, exist_ok=True) anno_file = osp.join(anno_dir, MotPath.GT_FILENAME) with open(anno_file, 'w', encoding="utf-8") as csv_file: @@ -291,6 +282,7 @@ class MotSeqGtConverter(Converter, CliPlugin): if track_id not in track_id_mapping: track_id_mapping[track_id] = len(track_id_mapping) track_id = track_id_mapping[track_id] + writer.writerow({ 'frame_id': frame_id, 'track_id': track_id, @@ -311,22 +303,13 @@ class MotSeqGtConverter(Converter, CliPlugin): if self._save_images: if item.has_image and item.image.has_data: - self._save_image(item, index=frame_id) + self._save_image(item, osp.join(self._images_dir, + '%06d%s' % (frame_id, self._find_image_ext(item)))) else: - log.debug("Item '%s' has no image" % item.id) + log.debug("Item '%s' has no image", item.id) labels_file = osp.join(anno_dir, MotPath.LABELS_FILE) with open(labels_file, 'w', encoding='utf-8') as f: f.write('\n'.join(l.name for l in extractor.categories()[AnnotationType.label].items) ) - - def _save_image(self, item, index): - if item.image.filename: - frame_id = osp.splitext(item.image.filename)[0] - else: - frame_id = item.id - frame_id = cast(frame_id, int, index) - image_filename = '%06d%s' % (frame_id, MotPath.IMAGE_EXT) - save_image(osp.join(self._images_dir, image_filename), - item.image.data) \ No newline at end of file diff --git a/datumaro/datumaro/plugins/openvino_launcher.py b/datumaro/datumaro/plugins/openvino_launcher.py index 10f12fea..4e150b03 100644 --- a/datumaro/datumaro/plugins/openvino_launcher.py +++ b/datumaro/datumaro/plugins/openvino_launcher.py @@ -6,17 +6,46 @@ # pylint: disable=exec-used import cv2 +import logging as log import numpy as np -import os import os.path as osp -import platform -import subprocess +import shutil -from openvino.inference_engine import IENetwork, IEPlugin +from openvino.inference_engine import IECore +from datumaro.components.cli_plugin import CliPlugin from datumaro.components.launcher import Launcher +class OpenVinoImporter(CliPlugin): + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-d', '--description', required=True, + help="Path to the model description file (.xml)") + parser.add_argument('-w', '--weights', required=True, + help="Path to the model weights file (.bin)") + parser.add_argument('-i', '--interpreter', required=True, + help="Path to the network output interprter script (.py)") + parser.add_argument('--device', default='CPU', + help="Target device (default: %(default)s)") + return parser + + @staticmethod + def copy_model(model_dir, model): + shutil.copy(model['description'], + osp.join(model_dir, osp.basename(model['description']))) + model['description'] = osp.basename(model['description']) + + shutil.copy(model['weights'], + osp.join(model_dir, osp.basename(model['weights']))) + model['weights'] = osp.basename(model['weights']) + + shutil.copy(model['interpreter'], + osp.join(model_dir, osp.basename(model['interpreter']))) + model['interpreter'] = osp.basename(model['interpreter']) + + class InterpreterScript: def __init__(self, path): with open(path, 'r') as f: @@ -25,13 +54,16 @@ class InterpreterScript: context = {} exec(script, context, context) - process_outputs = context['process_outputs'] - assert callable(process_outputs) + process_outputs = context.get('process_outputs') + if not callable(process_outputs): + raise Exception("Can't find 'process_outputs' function in " + "the interpreter script") self.__dict__['process_outputs'] = process_outputs get_categories = context.get('get_categories') - assert callable(get_categories) or get_categories is None - self.__dict__['get_categories'] = get_categories + assert get_categories is None or callable(get_categories) + if get_categories: + self.__dict__['get_categories'] = get_categories @staticmethod def get_categories(): @@ -39,52 +71,16 @@ class InterpreterScript: @staticmethod def process_outputs(inputs, outputs): - return [] + raise NotImplementedError( + "Function should be implemented in the interpreter script") -class OpenVinoLauncher(Launcher): - _DEFAULT_IE_PLUGINS_PATH = "/opt/intel/openvino_2019.1.144/deployment_tools/inference_engine/lib/intel64" - _IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", _DEFAULT_IE_PLUGINS_PATH) - @staticmethod - def _check_instruction_set(instruction): - return instruction == str.strip( - # Let's ignore a warning from bandit about using shell=True. - # In this case it isn't a security issue and we use some - # shell features like pipes. - subprocess.check_output( - 'lscpu | grep -o "{}" | head -1'.format(instruction), - shell=True).decode('utf-8') # nosec - ) - - @staticmethod - def make_plugin(device='cpu', plugins_path=_IE_PLUGINS_PATH): - if plugins_path is None or not osp.isdir(plugins_path): - raise Exception('Inference engine plugins directory "%s" not found' % \ - (plugins_path)) - - plugin = IEPlugin(device='CPU', plugin_dirs=[plugins_path]) - if (OpenVinoLauncher._check_instruction_set('avx2')): - plugin.add_cpu_extension(os.path.join(plugins_path, - 'libcpu_extension_avx2.so')) - elif (OpenVinoLauncher._check_instruction_set('sse4')): - plugin.add_cpu_extension(os.path.join(plugins_path, - 'libcpu_extension_sse4.so')) - elif platform.system() == 'Darwin': - plugin.add_cpu_extension(os.path.join(plugins_path, - 'libcpu_extension.dylib')) - else: - raise Exception('Inference engine requires support of avx2 or sse4') - - return plugin - - @staticmethod - def make_network(model, weights): - return IENetwork.from_ir(model=model, weights=weights) +class OpenVinoLauncher(Launcher): + cli_plugin = OpenVinoImporter - def __init__(self, description, weights, interpretation_script, - plugins_path=None, model_dir=None, **kwargs): - if model_dir is None: - model_dir = '' + def __init__(self, description, weights, interpreter, + plugins_path=None, device=None, model_dir=None): + model_dir = model_dir or '' if not osp.isfile(description): description = osp.join(model_dir, description) if not osp.isfile(description): @@ -97,34 +93,37 @@ class OpenVinoLauncher(Launcher): raise Exception('Failed to open model weights file "%s"' % \ (weights)) - if not osp.isfile(interpretation_script): - interpretation_script = \ - osp.join(model_dir, interpretation_script) - if not osp.isfile(interpretation_script): - raise Exception('Failed to open model interpretation script file "%s"' % \ - (interpretation_script)) + if not osp.isfile(interpreter): + interpreter = osp.join(model_dir, interpreter) + if not osp.isfile(interpreter): + raise Exception('Failed to open model interpreter script file "%s"' % \ + (interpreter)) - self._interpreter_script = InterpreterScript(interpretation_script) + self._interpreter = InterpreterScript(interpreter) - if plugins_path is None: - plugins_path = OpenVinoLauncher._IE_PLUGINS_PATH + self._device = device or 'CPU' - plugin = OpenVinoLauncher.make_plugin(plugins_path=plugins_path) - network = OpenVinoLauncher.make_network(description, weights) - self._network = network - self._plugin = plugin + self._ie = IECore() + if hasattr(self._ie, 'read_network'): + self._network = self._ie.read_network(description, weights) + else: # backward compatibility + from openvino.inference_engine import IENetwork + self._network = IENetwork.from_ir(description, weights) + self._check_model_support(self._network, self._device) self._load_executable_net() + def _check_model_support(self, net, device): + supported_layers = set(self._ie.query_network(net, device)) + not_supported_layers = set(net.layers) - supported_layers + if len(not_supported_layers) != 0: + log.error("The following layers are not supported " \ + "by the plugin for device '%s': %s." % \ + (device, ', '.join(not_supported_layers))) + raise NotImplementedError( + "Some layers are not supported on the device") + def _load_executable_net(self, batch_size=1): network = self._network - plugin = self._plugin - - supported_layers = plugin.get_supported_layers(network) - not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] - if len(not_supported_layers) != 0: - raise Exception('Following layers are not supported by the plugin' - ' for the specified device {}:\n {}'. format( \ - plugin.device, ", ".join(not_supported_layers))) iter_inputs = iter(network.inputs) self._input_blob_name = next(iter_inputs) @@ -142,14 +141,14 @@ class OpenVinoLauncher(Launcher): network.reshape({self._input_blob_name: self._input_layout}) self._batch_size = batch_size - self._net = plugin.load(network=network, num_requests=1) + self._net = self._ie.load_network(network=network, num_requests=1, + device_name=self._device) def infer(self, inputs): assert len(inputs.shape) == 4, \ "Expected an input image in (N, H, W, C) format, got %s" % \ - (inputs.shape) - assert inputs.shape[3] == 3, \ - "Expected BGR input" + (inputs.shape) + assert inputs.shape[3] == 3, "Expected BGR input, got %s" % inputs.shape n, c, h, w = self._input_layout if inputs.shape[1:3] != (h, w): @@ -181,12 +180,9 @@ class OpenVinoLauncher(Launcher): results = self.process_outputs(inputs, outputs) return results - def get_categories(self): - return self._interpreter_script.get_categories() + def categories(self): + return self._interpreter.get_categories() def process_outputs(self, inputs, outputs): - return self._interpreter_script.process_outputs(inputs, outputs) + return self._interpreter.process_outputs(inputs, outputs) - def preferred_input_size(self): - _, _, h, w = self._input_layout - return (h, w) diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/converter.py b/datumaro/datumaro/plugins/tf_detection_api_format/converter.py index 01e2bd0e..7ff3569d 100644 --- a/datumaro/datumaro/plugins/tf_detection_api_format/converter.py +++ b/datumaro/datumaro/plugins/tf_detection_api_format/converter.py @@ -5,6 +5,7 @@ import codecs from collections import OrderedDict +import hashlib import logging as log import os import os.path as osp @@ -14,11 +15,10 @@ from datumaro.components.extractor import (AnnotationType, DEFAULT_SUBSET_NAME, LabelCategories ) from datumaro.components.converter import Converter -from datumaro.components.cli_plugin import CliPlugin from datumaro.util.image import encode_image -from datumaro.util.mask_tools import merge_masks -from datumaro.util.annotation_tools import (compute_bbox, +from datumaro.util.annotation_util import (max_bbox, find_group_leader, find_instances) +from datumaro.util.mask_tools import merge_masks from datumaro.util.tf_util import import_tf as _import_tf from .format import DetectionApiPath @@ -45,26 +45,25 @@ def bytes_list_feature(value): def float_list_feature(value): return tf.train.Feature(float_list=tf.train.FloatList(value=value)) -class TfDetectionApiConverter(Converter, CliPlugin): +class TfDetectionApiConverter(Converter): + DEFAULT_IMAGE_EXT = DetectionApiPath.DEFAULT_IMAGE_EXT + @classmethod def build_cmdline_parser(cls, **kwargs): parser = super().build_cmdline_parser(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") parser.add_argument('--save-masks', action='store_true', help="Include instance masks (default: %(default)s)") return parser - def __init__(self, save_images=False, save_masks=False): - super().__init__() + def __init__(self, extractor, save_dir, save_masks=False, **kwargs): + super().__init__(extractor, save_dir, **kwargs) - self._save_images = save_images self._save_masks = save_masks - def __call__(self, extractor, save_dir): - os.makedirs(save_dir, exist_ok=True) + def apply(self): + os.makedirs(self._save_dir, exist_ok=True) - label_categories = extractor.categories().get(AnnotationType.label, + label_categories = self._extractor.categories().get(AnnotationType.label, LabelCategories()) get_label = lambda label_id: label_categories.items[label_id].name \ if label_id is not None else '' @@ -74,18 +73,18 @@ class TfDetectionApiConverter(Converter, CliPlugin): self._get_label = get_label self._get_label_id = map_label_id - subsets = extractor.subsets() + subsets = self._extractor.subsets() if len(subsets) == 0: subsets = [ None ] for subset_name in subsets: if subset_name: - subset = extractor.get_subset(subset_name) + subset = self._extractor.get_subset(subset_name) else: subset_name = DEFAULT_SUBSET_NAME - subset = extractor + subset = self._extractor - labelmap_path = osp.join(save_dir, DetectionApiPath.LABELMAP_FILE) + labelmap_path = osp.join(self._save_dir, DetectionApiPath.LABELMAP_FILE) with codecs.open(labelmap_path, 'w', encoding='utf8') as f: for label, idx in label_ids.items(): f.write( @@ -95,7 +94,7 @@ class TfDetectionApiConverter(Converter, CliPlugin): '}\n\n' ) - anno_path = osp.join(save_dir, '%s.tfrecord' % (subset_name)) + anno_path = osp.join(self._save_dir, '%s.tfrecord' % (subset_name)) with tf.io.TFRecordWriter(anno_path) as writer: for item in subset: tf_example = self._make_tf_example(item) @@ -112,7 +111,7 @@ class TfDetectionApiConverter(Converter, CliPlugin): anns = boxes + masks leader = find_group_leader(anns) - bbox = compute_bbox(anns) + bbox = max_bbox(anns) mask = None if self._save_masks: @@ -162,14 +161,12 @@ class TfDetectionApiConverter(Converter, CliPlugin): def _make_tf_example(self, item): features = { - 'image/source_id': bytes_feature(str(item.id).encode('utf-8')), + 'image/source_id': bytes_feature( + str(item.attributes.get('source_id') or '').encode('utf-8') + ), } - filename = '' - if item.has_image: - filename = item.image.filename - if not filename: - filename = item.id + DetectionApiPath.IMAGE_EXT + filename = self._make_image_filename(item) features['image/filename'] = bytes_feature(filename.encode('utf-8')) if not item.has_image: @@ -184,16 +181,18 @@ class TfDetectionApiConverter(Converter, CliPlugin): features.update({ 'image/encoded': bytes_feature(b''), - 'image/format': bytes_feature(b'') + 'image/format': bytes_feature(b''), + 'image/key/sha256': bytes_feature(b''), }) if self._save_images: if item.has_image and item.image.has_data: - fmt = DetectionApiPath.IMAGE_FORMAT - buffer = encode_image(item.image.data, DetectionApiPath.IMAGE_EXT) + buffer, fmt = self._save_image(item, filename) + key = hashlib.sha256(buffer).hexdigest() features.update({ 'image/encoded': bytes_feature(buffer), 'image/format': bytes_feature(fmt.encode('utf-8')), + 'image/key/sha256': bytes_feature(key.encode('utf8')), }) else: log.warning("Item '%s' has no image" % item.id) @@ -206,3 +205,13 @@ class TfDetectionApiConverter(Converter, CliPlugin): features=tf.train.Features(feature=features)) return tf_example + + def _save_image(self, item, path=None): + dst_ext = osp.splitext(osp.basename(path))[1] + fmt = DetectionApiPath.IMAGE_EXT_FORMAT.get(dst_ext) + if not fmt: + log.warning("Item '%s': can't find format string for the '%s' " + "image extension, the corresponding field will be empty." % \ + (item.id, dst_ext)) + buffer = encode_image(item.image.data, dst_ext) + return buffer, fmt \ No newline at end of file diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py b/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py index 0f4c474b..f91c8b72 100644 --- a/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py +++ b/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py @@ -85,6 +85,10 @@ class TfDetectionApiExtractor(SourceExtractor): 'image/width': tf.io.FixedLenFeature([], tf.int64), 'image/encoded': tf.io.FixedLenFeature([], tf.string), 'image/format': tf.io.FixedLenFeature([], tf.string), + + # use varlen to avoid errors when this field is missing + 'image/key/sha256': tf.io.VarLenFeature(tf.string), + # Object boxes and classes. 'image/object/bbox/xmin': tf.io.VarLenFeature(tf.float32), 'image/object/bbox/xmax': tf.io.VarLenFeature(tf.float32), @@ -117,7 +121,6 @@ class TfDetectionApiExtractor(SourceExtractor): frame_width = tf.cast( parsed_record['image/width'], tf.int64).numpy().item() frame_image = parsed_record['image/encoded'].numpy() - frame_format = parsed_record['image/format'].numpy().decode('utf-8') xmins = tf.sparse.to_dense( parsed_record['image/object/bbox/xmin']).numpy() ymins = tf.sparse.to_dense( @@ -145,9 +148,7 @@ class TfDetectionApiExtractor(SourceExtractor): continue dataset_labels[label] = label_id - 1 - item_id = frame_id - if not item_id: - item_id = osp.splitext(frame_filename)[0] + item_id = osp.splitext(frame_filename)[0] annotations = [] for shape_id, shape in enumerate( @@ -178,7 +179,7 @@ class TfDetectionApiExtractor(SourceExtractor): image_size = (frame_height, frame_width) image_params = {} - if frame_image and frame_format: + if frame_image: image_params['data'] = lazy_image(frame_image, decode_image) if frame_filename: image_params['path'] = osp.join(images_dir, frame_filename) @@ -188,6 +189,7 @@ class TfDetectionApiExtractor(SourceExtractor): image = Image(**image_params, size=image_size) dataset_items.append(DatasetItem(id=item_id, subset=subset, - image=image, annotations=annotations)) + image=image, annotations=annotations, + attributes={'source_id': frame_id})) return dataset_items, dataset_labels diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/format.py b/datumaro/datumaro/plugins/tf_detection_api_format/format.py index 9e31212e..829a89e4 100644 --- a/datumaro/datumaro/plugins/tf_detection_api_format/format.py +++ b/datumaro/datumaro/plugins/tf_detection_api_format/format.py @@ -7,7 +7,7 @@ class DetectionApiPath: IMAGES_DIR = 'images' ANNOTATIONS_DIR = 'annotations' - IMAGE_EXT = '.jpg' - IMAGE_FORMAT = 'jpeg' + DEFAULT_IMAGE_EXT = '.jpg' + IMAGE_EXT_FORMAT = {'.jpg': 'jpeg', '.png': 'png'} LABELMAP_FILE = 'label_map.pbtxt' \ No newline at end of file diff --git a/datumaro/datumaro/plugins/transforms.py b/datumaro/datumaro/plugins/transforms.py index d37e284a..82493610 100644 --- a/datumaro/datumaro/plugins/transforms.py +++ b/datumaro/datumaro/plugins/transforms.py @@ -7,6 +7,7 @@ from enum import Enum import logging as log import os.path as osp import random +import re import pycocotools.mask as mask_utils @@ -16,7 +17,7 @@ from datumaro.components.extractor import (Transform, AnnotationType, ) from datumaro.components.cli_plugin import CliPlugin import datumaro.util.mask_tools as mask_tools -from datumaro.util.annotation_tools import find_group_leader, find_instances +from datumaro.util.annotation_util import find_group_leader, find_instances class CropCoveredSegments(Transform, CliPlugin): @@ -321,6 +322,7 @@ class RandomSplit(Transform, CliPlugin): parser = super().build_cmdline_parser(**kwargs) parser.add_argument('-s', '--subset', action='append', type=cls._split_arg, dest='splits', + default=[('train', 0.67), ('test', 0.33)], help="Subsets in the form of: ':' (repeatable)") parser.add_argument('--seed', type=int, help="Random seed") return parser @@ -364,12 +366,56 @@ class RandomSplit(Transform, CliPlugin): class IdFromImageName(Transform, CliPlugin): def transform_item(self, item): - name = item.id - if item.has_image and item.image.filename: - name = osp.splitext(item.image.filename)[0] - return self.wrap_item(item, id=name) + if item.has_image and item.image.path: + name = osp.splitext(osp.basename(item.image.path))[0] + return self.wrap_item(item, id=name) + else: + log.debug("Can't change item id for item '%s': " + "item has no image info" % item.id) + return item + +class Rename(Transform, CliPlugin): + """ + Renames items in the dataset. Supports regular expressions. + The first character in the expression is a delimiter for + the pattern and replacement parts. Replacement part can also + contain string.format tokens with 'item' object available.|n + |n + Examples:|n + - Replace 'pattern' with 'replacement':|n + |s|srename -e '|pattern|replacement|'|n + - Remove 'frame_' from item ids:|n + |s|srename -e '|frame_(\d+)|\\1|' + """ + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument('-e', '--regex', + help="Regex for renaming.") + return parser + + def __init__(self, extractor, regex): + super().__init__(extractor) + + assert regex and isinstance(regex, str) + parts = regex.split(regex[0], maxsplit=3) + regex, sub = parts[1:3] + self._re = re.compile(regex) + self._sub = sub + + def transform_item(self, item): + return self.wrap_item(item, id=self._re.sub(self._sub, item.id) \ + .format(item=item)) class RemapLabels(Transform, CliPlugin): + """ + Changes labels in the dataset.|n + Examples:|n + - Rename 'person' to 'car' and 'cat' to 'dog', keep 'bus', remove others:|n + |s|sremap_labels -l person:car -l bus:bus -l cat:dog --default delete + """ + DefaultAction = Enum('DefaultAction', ['keep', 'delete']) @staticmethod @@ -389,7 +435,7 @@ class RemapLabels(Transform, CliPlugin): parser.add_argument('--default', choices=[a.name for a in cls.DefaultAction], default=cls.DefaultAction.keep.name, - help="Action for unspecified labels") + help="Action for unspecified labels (default: %(default)s)") return parser def __init__(self, extractor, mapping, default=None): @@ -465,7 +511,6 @@ class RemapLabels(Transform, CliPlugin): return self._categories def transform_item(self, item): - # TODO: provide non-inplace version annotations = [] for ann in item.annotations: if ann.type in { AnnotationType.label, AnnotationType.mask, @@ -474,9 +519,7 @@ class RemapLabels(Transform, CliPlugin): } and ann.label is not None: conv_label = self._map_id(ann.label) if conv_label is not None: - ann._label = conv_label - annotations.append(ann) + annotations.append(ann.wrap(label=conv_label)) else: - annotations.append(ann) - item._annotations = annotations - return item \ No newline at end of file + annotations.append(ann.wrap()) + return item.wrap(annotations=annotations) \ No newline at end of file diff --git a/datumaro/datumaro/plugins/voc_format/converter.py b/datumaro/datumaro/plugins/voc_format/converter.py index 01394a5d..65e586d8 100644 --- a/datumaro/datumaro/plugins/voc_format/converter.py +++ b/datumaro/datumaro/plugins/voc_format/converter.py @@ -3,24 +3,23 @@ # # SPDX-License-Identifier: MIT +import logging as log +import os +import os.path as osp from collections import OrderedDict, defaultdict from enum import Enum from itertools import chain -import logging as log + from lxml import etree as ET -import os -import os.path as osp -from datumaro.components.cli_plugin import CliPlugin from datumaro.components.converter import Converter from datumaro.components.extractor import (DEFAULT_SUBSET_NAME, AnnotationType, - LabelCategories, CompiledMask, -) + CompiledMask, LabelCategories) +from datumaro.util import find, str_to_bool from datumaro.util.image import save_image from datumaro.util.mask_tools import paint_mask, remap_mask -from .format import (VocTask, VocPath, - VocInstColormap, VocPose, +from .format import (VocTask, VocPath, VocInstColormap, parse_label_map, make_voc_label_map, make_voc_categories, write_label_map ) @@ -48,11 +47,49 @@ def _write_xml_bbox(bbox, parent_elem): return bbox_elem -LabelmapType = Enum('LabelmapType', ['voc', 'source', 'guess']) +LabelmapType = Enum('LabelmapType', ['voc', 'source']) + +class VocConverter(Converter): + DEFAULT_IMAGE_EXT = VocPath.IMAGE_EXT + + @staticmethod + def _split_tasks_string(s): + return [VocTask[i.strip()] for i in s.split(',')] + + @staticmethod + def _get_labelmap(s): + if osp.isfile(s): + return s + try: + return LabelmapType[s].name + except KeyError: + import argparse + raise argparse.ArgumentTypeError() + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + + parser.add_argument('--apply-colormap', type=str_to_bool, default=True, + help="Use colormap for class and instance masks " + "(default: %(default)s)") + parser.add_argument('--label-map', type=cls._get_labelmap, default=None, + help="Labelmap file path or one of %s" % \ + ', '.join(t.name for t in LabelmapType)) + parser.add_argument('--allow-attributes', + type=str_to_bool, default=True, + help="Allow export of attributes (default: %(default)s)") + parser.add_argument('--tasks', type=cls._split_tasks_string, + help="VOC task filter, comma-separated list of {%s} " + "(default: all)" % ', '.join(t.name for t in VocTask)) + + return parser -class _Converter: def __init__(self, extractor, save_dir, - tasks=None, apply_colormap=True, save_images=False, label_map=None): + tasks=None, apply_colormap=True, label_map=None, + allow_attributes=True, **kwargs): + super().__init__(extractor, save_dir, **kwargs) + assert tasks is None or isinstance(tasks, (VocTask, list, set)) if tasks is None: tasks = set(VocTask) @@ -62,19 +99,19 @@ class _Converter: tasks = set(t if t in VocTask else VocTask[t] for t in tasks) self._tasks = tasks - self._extractor = extractor - self._save_dir = save_dir self._apply_colormap = apply_colormap - self._save_images = save_images + self._allow_attributes = allow_attributes + if label_map is None: + label_map = LabelmapType.source self._load_categories(label_map) - def convert(self): - self.init_dirs() + def apply(self): + self.make_dirs() self.save_subsets() self.save_label_map() - def init_dirs(self): + def make_dirs(self): save_dir = self._save_dir subsets_dir = osp.join(save_dir, VocPath.SUBSETS_DIR) cls_subsets_dir = osp.join(subsets_dir, @@ -110,15 +147,11 @@ class _Converter: self._images_dir = images_dir def get_label(self, label_id): - return self._strip_label(self._extractor. \ - categories()[AnnotationType.label].items[label_id].name) + return self._extractor. \ + categories()[AnnotationType.label].items[label_id].name def save_subsets(self): - subsets = self._extractor.subsets() - if len(subsets) == 0: - subsets = [ None ] - - for subset_name in subsets: + for subset_name in self._extractor.subsets() or [None]: if subset_name: subset = self._extractor.get_subset(subset_name) else: @@ -134,20 +167,13 @@ class _Converter: for item in subset: log.debug("Converting item '%s'", item.id) - image_filename = '' - if item.has_image: - image_filename = item.image.filename + image_filename = self._make_image_filename(item) if self._save_images: if item.has_image and item.image.has_data: - if image_filename: - image_filename = osp.splitext(image_filename)[0] - else: - image_filename = item.id - image_filename += VocPath.IMAGE_EXT - save_image(osp.join(self._images_dir, image_filename), - item.image.data) + self._save_image(item, + osp.join(self._images_dir, image_filename)) else: - log.debug("Item '%s' has no image" % item.id) + log.debug("Item '%s' has no image", item.id) labels = [] bboxes = [] @@ -160,7 +186,9 @@ class _Converter: elif a.type == AnnotationType.mask: masks.append(a) - if len(bboxes) != 0: + if self._tasks is None and bboxes or \ + self._tasks & {VocTask.detection, VocTask.person_layout, + VocTask.action_classification}: root_elem = ET.Element('annotation') if '_' in item.id: folder = item.id[ : item.id.find('_')] @@ -207,13 +235,12 @@ class _Converter: obj_elem = ET.SubElement(root_elem, 'object') - obj_label = self.get_label(obj.label) + obj_label = self.get_label(obj.label) ET.SubElement(obj_elem, 'name').text = obj_label if 'pose' in attr: - pose = _convert_attr('pose', attr, - lambda v: VocPose[v], VocPose.Unspecified) - ET.SubElement(obj_elem, 'pose').text = pose.name + ET.SubElement(obj_elem, 'pose').text = \ + str(attr['pose']) if 'truncated' in attr: truncated = _convert_attr('truncated', attr, int, 0) @@ -258,11 +285,26 @@ class _Converter: if len(actions_elem) != 0: obj_elem.append(actions_elem) - if self._tasks & {None, - VocTask.detection, - VocTask.person_layout, + if self._allow_attributes: + native_attrs = {'difficult', 'pose', + 'truncated', 'occluded' } + native_attrs.update(label_actions) + + attrs_elem = ET.Element('attributes') + for k, v in attr.items(): + if k in native_attrs: + continue + attr_elem = ET.SubElement(attrs_elem, 'attribute') + ET.SubElement(attr_elem, 'name').text = str(k) + ET.SubElement(attr_elem, 'value').text = str(v) + if len(attrs_elem): + obj_elem.append(attrs_elem) + + if self._tasks & {VocTask.detection, VocTask.person_layout, VocTask.action_classification}: - with open(osp.join(self._ann_dir, item.id + '.xml'), 'w') as f: + ann_path = osp.join(self._ann_dir, item.id + '.xml') + os.makedirs(osp.dirname(ann_path), exist_ok=True) + with open(ann_path, 'w') as f: f.write(ET.tostring(root_elem, encoding='unicode', pretty_print=True)) @@ -301,19 +343,16 @@ class _Converter: action_list[item.id] = None segm_list[item.id] = None - if self._tasks & {None, - VocTask.classification, - VocTask.detection, - VocTask.action_classification, - VocTask.person_layout}: + if self._tasks & {VocTask.classification, VocTask.detection, + VocTask.action_classification, VocTask.person_layout}: self.save_clsdet_lists(subset_name, clsdet_list) - if self._tasks & {None, VocTask.classification}: + if self._tasks & {VocTask.classification}: self.save_class_lists(subset_name, class_lists) - if self._tasks & {None, VocTask.action_classification}: + if self._tasks & {VocTask.action_classification}: self.save_action_lists(subset_name, action_list) - if self._tasks & {None, VocTask.person_layout}: + if self._tasks & {VocTask.person_layout}: self.save_layout_lists(subset_name, layout_list) - if self._tasks & {None, VocTask.segmentation}: + if self._tasks & {VocTask.segmentation}: self.save_segm_lists(subset_name, segm_list) def save_action_lists(self, subset_name, action_list): @@ -357,8 +396,7 @@ class _Converter: for item, item_labels in class_lists.items(): if not item_labels: continue - item_labels = [self._strip_label(self.get_label(l)) - for l in item_labels] + item_labels = [self.get_label(l) for l in item_labels] presented = label in item_labels f.write('%s % d\n' % (item, 1 if presented else -1)) @@ -404,50 +442,39 @@ class _Converter: if colormap is None: colormap = self._categories[AnnotationType.mask].colormap mask = paint_mask(mask, colormap) - save_image(path, mask) + save_image(path, mask, create_dir=True) def save_label_map(self): path = osp.join(self._save_dir, VocPath.LABELMAP_FILE) write_label_map(path, self._label_map) - @staticmethod - def _strip_label(label): - return label.lower().strip() - - def _load_categories(self, label_map_source=None): + def _load_categories(self, label_map_source): if label_map_source == LabelmapType.voc.name: - # strictly use VOC default labelmap + # use the default VOC colormap label_map = make_voc_label_map() - elif label_map_source == LabelmapType.source.name: - # generate colormap from the input dataset + elif label_map_source == LabelmapType.source.name and \ + AnnotationType.mask not in self._extractor.categories(): + # generate colormap for input labels labels = self._extractor.categories() \ .get(AnnotationType.label, LabelCategories()) + label_map = OrderedDict((item.name, [None, [], []]) + for item in labels.items) + + elif label_map_source == LabelmapType.source.name and \ + AnnotationType.mask in self._extractor.categories(): + # use source colormap + labels = self._extractor.categories()[AnnotationType.label] + colors = self._extractor.categories()[AnnotationType.mask] label_map = OrderedDict() - label_map['background'] = [None, [], []] - for item in labels.items: - label_map[self._strip_label(item.name)] = [None, [], []] - - elif label_map_source in [LabelmapType.guess.name, None]: - # generate colormap for union of VOC and input dataset labels - label_map = make_voc_label_map() - - rebuild_colormap = False - source_labels = self._extractor.categories() \ - .get(AnnotationType.label, LabelCategories()) - for label in source_labels.items: - label_name = self._strip_label(label.name) - if label_name not in label_map: - rebuild_colormap = True - if label.attributes or label_name not in label_map: - label_map[label_name] = [None, [], label.attributes] - - if rebuild_colormap: - for item in label_map.values(): - item[0] = None + for idx, item in enumerate(labels.items): + color = colors.colormap.get(idx) + if color is not None: + label_map[item.name] = [color, [], []] elif isinstance(label_map_source, dict): - label_map = label_map_source + label_map = OrderedDict( + sorted(label_map_source.items(), key=lambda e: e[0])) elif isinstance(label_map_source, str) and osp.isfile(label_map_source): label_map = parse_label_map(label_map_source) @@ -457,39 +484,51 @@ class _Converter: "expected one of %s or a file path" % \ ', '.join(t.name for t in LabelmapType)) + # There must always be a label with color (0, 0, 0) at index 0 + bg_label = find(label_map.items(), lambda x: x[1][0] == (0, 0, 0)) + if bg_label is not None: + bg_label = bg_label[0] + else: + bg_label = 'background' + if bg_label not in label_map: + has_colors = any(v[0] is not None for v in label_map.values()) + color = (0, 0, 0) if has_colors else None + label_map[bg_label] = [color, [], []] + label_map.move_to_end(bg_label, last=False) + self._categories = make_voc_categories(label_map) - self._label_map = label_map + # Update colors with assigned values colormap = self._categories[AnnotationType.mask].colormap for label_id, color in colormap.items(): label_desc = label_map[ self._categories[AnnotationType.label].items[label_id].name] label_desc[0] = color + self._label_map = label_map self._label_id_mapping = self._make_label_id_map() def _is_label(self, s): - return self._label_map.get(self._strip_label(s)) is not None + return self._label_map.get(s) is not None def _is_part(self, s): - s = self._strip_label(s) for label_desc in self._label_map.values(): if s in label_desc[1]: return True return False def _is_action(self, label, s): - return self._strip_label(s) in self._get_actions(label) + return s in self._get_actions(label) def _get_actions(self, label): - label_desc = self._label_map.get(self._strip_label(label)) + label_desc = self._label_map.get(label) if not label_desc: return [] return label_desc[2] def _make_label_id_map(self): source_labels = { - id: self._strip_label(label.name) for id, label in + id: label.name for id, label in enumerate(self._extractor.categories().get( AnnotationType.label, LabelCategories()).items) } @@ -525,76 +564,27 @@ class _Converter: def _remap_mask(self, mask): return remap_mask(mask, self._label_id_mapping) -class VocConverter(Converter, CliPlugin): - @staticmethod - def _split_tasks_string(s): - return [VocTask[i.strip()] for i in s.split(',')] - - @staticmethod - def _get_labelmap(s): - if osp.isfile(s): - return s - try: - return LabelmapType[s].name - except KeyError: - import argparse - raise argparse.ArgumentTypeError() - - @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().build_cmdline_parser(**kwargs) - - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - parser.add_argument('--apply-colormap', type=bool, default=True, - help="Use colormap for class and instance masks " - "(default: %(default)s)") - parser.add_argument('--label-map', type=cls._get_labelmap, default=None, - help="Labelmap file path or one of %s" % \ - ', '.join(t.name for t in LabelmapType)) - parser.add_argument('--tasks', type=cls._split_tasks_string, - default=None, - help="VOC task filter, comma-separated list of {%s} " - "(default: all)" % ', '.join([t.name for t in VocTask])) - - return parser - - def __init__(self, tasks=None, save_images=False, - apply_colormap=False, label_map=None): - super().__init__() - - self._options = { - 'tasks': tasks, - 'save_images': save_images, - 'apply_colormap': apply_colormap, - 'label_map': label_map, - } - - def __call__(self, extractor, save_dir): - converter = _Converter(extractor, save_dir, **self._options) - converter.convert() - class VocClassificationConverter(VocConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = VocTask.classification - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class VocDetectionConverter(VocConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = VocTask.detection - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class VocLayoutConverter(VocConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = VocTask.person_layout - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class VocActionConverter(VocConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = VocTask.action_classification - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class VocSegmentationConverter(VocConverter): - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): kwargs['tasks'] = VocTask.segmentation - super().__init__(**kwargs) + super().__init__(*args, **kwargs) diff --git a/datumaro/datumaro/plugins/voc_format/extractor.py b/datumaro/datumaro/plugins/voc_format/extractor.py index dd340db0..669d7810 100644 --- a/datumaro/datumaro/plugins/voc_format/extractor.py +++ b/datumaro/datumaro/plugins/voc_format/extractor.py @@ -32,8 +32,15 @@ class _VocExtractor(SourceExtractor): super().__init__(subset=osp.splitext(osp.basename(path))[0]) self._categories = self._load_categories(self._dataset_dir) - log.debug("Loaded labels: %s", ', '.join("'%s'" % l.name - for l in self._categories[AnnotationType.label].items)) + + label_color = lambda label_idx: \ + self._categories[AnnotationType.mask].colormap.get(label_idx, None) + log.debug("Loaded labels: %s" % ', '.join( + "'%s' %s" % (l.name, ('(%s, %s, %s)' % c) if c else '') + for i, l, c in ((i, l, label_color(i)) for i, l in enumerate( + self._categories[AnnotationType.label].items + )) + )) self._items = self._load_subset_list(path) def categories(self): @@ -64,6 +71,7 @@ class VocClassificationExtractor(_VocExtractor): def __iter__(self): raw_anns = self._load_annotations() for item_id in self._items: + log.debug("Reading item '%s'" % item_id) image = osp.join(self._dataset_dir, VocPath.IMAGES_DIR, item_id + VocPath.IMAGE_EXT) anns = self._parse_annotations(raw_anns, item_id) @@ -99,8 +107,9 @@ class _VocXmlExtractor(_VocExtractor): anno_dir = osp.join(self._dataset_dir, VocPath.ANNOTATIONS_DIR) for item_id in self._items: - image = osp.join(self._dataset_dir, VocPath.IMAGES_DIR, - item_id + VocPath.IMAGE_EXT) + log.debug("Reading item '%s'" % item_id) + image = item_id + VocPath.IMAGE_EXT + height, width = 0, 0 anns = [] ann_file = osp.join(anno_dir, item_id + '.xml') @@ -112,11 +121,15 @@ class _VocXmlExtractor(_VocExtractor): width = root_elem.find('size/width') if width is not None: width = int(width.text) - if height and width: - image = Image(path=image, size=(height, width)) - + filename_elem = root_elem.find('filename') + if filename_elem is not None: + image = filename_elem.text anns = self._parse_annotations(root_elem) + image = osp.join(self._dataset_dir, VocPath.IMAGES_DIR, image) + if height and width: + image = Image(path=image, size=(height, width)) + yield DatasetItem(id=item_id, subset=self._subset, image=image, annotations=anns) @@ -185,6 +198,12 @@ class _VocXmlExtractor(_VocExtractor): item_annotations.append(Bbox(*part_bbox, label=part_label_id, group=group)) + attributes_elem = object_elem.find('attributes') + if attributes_elem is not None: + for attr_elem in attributes_elem.iter('attribute'): + attributes[attr_elem.find('name').text] = \ + attr_elem.find('value').text + if self._task is VocTask.person_layout and not has_parts: continue if self._task is VocTask.action_classification and not actions: @@ -219,6 +238,7 @@ class VocActionExtractor(_VocXmlExtractor): class VocSegmentationExtractor(_VocExtractor): def __iter__(self): for item_id in self._items: + log.debug("Reading item '%s'" % item_id) image = osp.join(self._dataset_dir, VocPath.IMAGES_DIR, item_id + VocPath.IMAGE_EXT) anns = self._load_annotations(item_id) diff --git a/datumaro/datumaro/plugins/voc_format/format.py b/datumaro/datumaro/plugins/voc_format/format.py index 5af79f2d..471866be 100644 --- a/datumaro/datumaro/plugins/voc_format/format.py +++ b/datumaro/datumaro/plugins/voc_format/format.py @@ -137,6 +137,9 @@ def parse_label_map(path): label_desc = line.strip().split(':') name = label_desc[0] + if name in label_map: + raise ValueError("Label '%s' is already defined" % name) + if 1 < len(label_desc) and len(label_desc[1]) != 0: color = label_desc[1].split(',') assert len(color) == 3, \ @@ -173,7 +176,6 @@ def write_label_map(path, label_map): f.write('%s\n' % ':'.join([label_name, color_rgb, parts, actions])) -# pylint: disable=pointless-statement def make_voc_categories(label_map=None): if label_map is None: label_map = make_voc_label_map() @@ -190,16 +192,15 @@ def make_voc_categories(label_map=None): label_categories.add(part) categories[AnnotationType.label] = label_categories - has_colors = sum(v[0] is not None for v in label_map.values()) - if not has_colors: + has_colors = any(v[0] is not None for v in label_map.values()) + if not has_colors: # generate new colors colormap = generate_colormap(len(label_map)) - else: + else: # only copy defined colors label_id = lambda label: label_categories.find(label)[0] colormap = { label_id(name): desc[0] - for name, desc in label_map.items() } + for name, desc in label_map.items() if desc[0] is not None } mask_categories = MaskCategories(colormap) - mask_categories.inverse_colormap # force init + mask_categories.inverse_colormap # pylint: disable=pointless-statement categories[AnnotationType.mask] = mask_categories return categories -# pylint: enable=pointless-statement \ No newline at end of file diff --git a/datumaro/datumaro/plugins/yolo_format/converter.py b/datumaro/datumaro/plugins/yolo_format/converter.py index de30d7d7..a8ed3524 100644 --- a/datumaro/datumaro/plugins/yolo_format/converter.py +++ b/datumaro/datumaro/plugins/yolo_format/converter.py @@ -3,15 +3,13 @@ # # SPDX-License-Identifier: MIT -from collections import OrderedDict import logging as log import os import os.path as osp +from collections import OrderedDict from datumaro.components.converter import Converter from datumaro.components.extractor import AnnotationType -from datumaro.components.cli_plugin import CliPlugin -from datumaro.util.image import save_image from .format import YoloPath @@ -26,21 +24,14 @@ def _make_yolo_bbox(img_size, box): h = (box[3] - box[1]) / img_size[1] return x, y, w, h -class YoloConverter(Converter, CliPlugin): +class YoloConverter(Converter): # https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects + DEFAULT_IMAGE_EXT = '.jpg' - @classmethod - def build_cmdline_parser(cls, **kwargs): - parser = super().build_cmdline_parser(**kwargs) - parser.add_argument('--save-images', action='store_true', - help="Save images (default: %(default)s)") - return parser + def apply(self): + extractor = self._extractor + save_dir = self._save_dir - def __init__(self, save_images=False): - super().__init__() - self._save_images = save_images - - def __call__(self, extractor, save_dir): os.makedirs(save_dir, exist_ok=True) label_categories = extractor.categories()[AnnotationType.label] @@ -50,13 +41,9 @@ class YoloConverter(Converter, CliPlugin): f.writelines('%s\n' % l[0] for l in sorted(label_ids.items(), key=lambda x: x[1])) - subsets = extractor.subsets() - if len(subsets) == 0: - subsets = [ None ] - subset_lists = OrderedDict() - for subset_name in subsets: + for subset_name in extractor.subsets() or [None]: if subset_name and subset_name in YoloPath.SUBSET_NAMES: subset = extractor.get_subset(subset_name) elif not subset_name: @@ -80,15 +67,10 @@ class YoloConverter(Converter, CliPlugin): "item has no image info" % item.id) height, width = item.image.size - image_name = item.image.filename - item_name = osp.splitext(item.image.filename)[0] + image_name = self._make_image_filename(item) if self._save_images: if item.has_image and item.image.has_data: - if not item_name: - item_name = item.id - image_name = item_name + '.jpg' - save_image(osp.join(subset_dir, image_name), - item.image.data) + self._save_image(item, osp.join(subset_dir, image_name)) else: log.warning("Item '%s' has no image" % item.id) image_paths[item.id] = osp.join('data', @@ -105,7 +87,8 @@ class YoloConverter(Converter, CliPlugin): yolo_bb = ' '.join('%.6f' % p for p in yolo_bb) yolo_annotation += '%s %s\n' % (bbox.label, yolo_bb) - annotation_path = osp.join(subset_dir, '%s.txt' % item_name) + annotation_path = osp.join(subset_dir, '%s.txt' % item.id) + os.makedirs(osp.dirname(annotation_path), exist_ok=True) with open(annotation_path, 'w') as f: f.write(yolo_annotation) @@ -122,4 +105,4 @@ class YoloConverter(Converter, CliPlugin): osp.join('data', subset_list_name))) f.write('names = %s\n' % osp.join('data', 'obj.names')) - f.write('backup = backup/\n') \ No newline at end of file + f.write('backup = backup/\n') diff --git a/datumaro/datumaro/plugins/yolo_format/extractor.py b/datumaro/datumaro/plugins/yolo_format/extractor.py index 5e2c61b3..9e34508c 100644 --- a/datumaro/datumaro/plugins/yolo_format/extractor.py +++ b/datumaro/datumaro/plugins/yolo_format/extractor.py @@ -10,6 +10,7 @@ import re from datumaro.components.extractor import (SourceExtractor, Extractor, DatasetItem, AnnotationType, Bbox, LabelCategories ) +from datumaro.util import split_path from datumaro.util.image import Image from .format import YoloPath @@ -83,14 +84,14 @@ class YoloExtractor(SourceExtractor): config_path) for subset_name, list_path in subsets.items(): - list_path = self._make_local_path(list_path) + list_path = osp.join(self._path, self.localize_path(list_path)) if not osp.isfile(list_path): raise Exception("Not found '%s' subset list file" % subset_name) subset = YoloExtractor.Subset(subset_name, self) with open(list_path, 'r') as f: subset.items = OrderedDict( - (osp.splitext(osp.basename(p.strip()))[0], p.strip()) + (self.name_from_path(p), self.localize_path(p)) for p in f ) subsets[subset_name] = subset @@ -99,25 +100,38 @@ class YoloExtractor(SourceExtractor): self._categories = { AnnotationType.label: - self._load_categories(self._make_local_path(names_path)) + self._load_categories( + osp.join(self._path, self.localize_path(names_path))) } - def _make_local_path(self, path): + @staticmethod + def localize_path(path): + path = path.strip() default_base = osp.join('data', '') if path.startswith(default_base): # default path path = path[len(default_base) : ] - return osp.join(self._path, path) # relative or absolute path + return path + + @classmethod + def name_from_path(cls, path): + path = cls.localize_path(path) + parts = split_path(path) + if 1 < len(parts) and not osp.isabs(path): + # NOTE: when path is like [data/]/ + # drop everything but + # can be , so no just basename() + path = osp.join(*parts[1:]) + return osp.splitext(path)[0] def _get(self, item_id, subset_name): subset = self._subsets[subset_name] item = subset.items[item_id] if isinstance(item, str): - image_path = self._make_local_path(item) image_size = self._image_info.get(item_id) - image = Image(path=image_path, size=image_size) + image = Image(path=osp.join(self._path, item), size=image_size) - anno_path = osp.splitext(image_path)[0] + '.txt' + anno_path = osp.splitext(image.path)[0] + '.txt' annotations = self._parse_annotations(anno_path, image) item = DatasetItem(id=item_id, subset=subset_name, @@ -137,7 +151,10 @@ class YoloExtractor(SourceExtractor): annotations = [] if lines: - image_height, image_width = image.size # use image info late + size = image.size # use image info as late as possible + if size is None: + raise Exception("Can't find image info for '%s'" % image.path) + image_height, image_width = size for line in lines: label_id, xc, yc, w, h = line.split() label_id = int(label_id) diff --git a/datumaro/datumaro/util/__init__.py b/datumaro/datumaro/util/__init__.py index 624284c2..293bb5f6 100644 --- a/datumaro/datumaro/util/__init__.py +++ b/datumaro/datumaro/util/__init__.py @@ -5,6 +5,7 @@ import os import os.path as osp +from itertools import islice def find(iterable, pred=lambda x: True, default=None): @@ -59,4 +60,31 @@ def to_snake_case(s): name.append(char.lower()) else: name.append(char) - return ''.join(name) \ No newline at end of file + return ''.join(name) + +def pairs(iterable): + a = iter(iterable) + return zip(a, a) + +def take_by(iterable, count): + """ + Returns elements from the input iterable by batches of N items. + ('abcdefg', 3) -> ['a', 'b', 'c'], ['d', 'e', 'f'], ['g'] + """ + + it = iter(iterable) + while True: + batch = list(islice(it, count)) + if len(batch) == 0: + break + + yield batch + +def str_to_bool(s): + t = s.lower() + if t in {'true', '1', 'ok', 'yes', 'y'}: + return True + elif t in {'false', '0', 'no', 'n'}: + return False + else: + raise ValueError("Can't convert value '%s' to bool" % s) diff --git a/datumaro/datumaro/util/annotation_tools.py b/datumaro/datumaro/util/annotation_tools.py deleted file mode 100644 index d0fb1f64..00000000 --- a/datumaro/datumaro/util/annotation_tools.py +++ /dev/null @@ -1,29 +0,0 @@ - -# Copyright (C) 2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from itertools import groupby - - -def find_instances(instance_anns): - instance_anns = sorted(instance_anns, key=lambda a: a.group) - ann_groups = [] - for g_id, group in groupby(instance_anns, lambda a: a.group): - if not g_id: - ann_groups.extend(([a] for a in group)) - else: - ann_groups.append(list(group)) - - return ann_groups - -def find_group_leader(group): - return max(group, key=lambda x: x.get_area()) - -def compute_bbox(annotations): - boxes = [ann.get_bbox() for ann in annotations] - x0 = min((b[0] for b in boxes), default=0) - y0 = min((b[1] for b in boxes), default=0) - x1 = max((b[0] + b[2] for b in boxes), default=0) - y1 = max((b[1] + b[3] for b in boxes), default=0) - return [x0, y0, x1 - x0, y1 - y0] \ No newline at end of file diff --git a/datumaro/datumaro/util/annotation_util.py b/datumaro/datumaro/util/annotation_util.py new file mode 100644 index 00000000..38a2c814 --- /dev/null +++ b/datumaro/datumaro/util/annotation_util.py @@ -0,0 +1,213 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from itertools import groupby + +import numpy as np + +from datumaro.components.extractor import _Shape, Mask, AnnotationType, RleMask +from datumaro.util.mask_tools import mask_to_rle + + +def find_instances(instance_anns): + instance_anns = sorted(instance_anns, key=lambda a: a.group) + ann_groups = [] + for g_id, group in groupby(instance_anns, lambda a: a.group): + if not g_id: + ann_groups.extend(([a] for a in group)) + else: + ann_groups.append(list(group)) + + return ann_groups + +def find_group_leader(group): + return max(group, key=lambda x: x.get_area()) + +def _get_bbox(ann): + if isinstance(ann, (_Shape, Mask)): + return ann.get_bbox() + else: + return ann + +def max_bbox(annotations): + boxes = [_get_bbox(ann) for ann in annotations] + x0 = min((b[0] for b in boxes), default=0) + y0 = min((b[1] for b in boxes), default=0) + x1 = max((b[0] + b[2] for b in boxes), default=0) + y1 = max((b[1] + b[3] for b in boxes), default=0) + return [x0, y0, x1 - x0, y1 - y0] + +def mean_bbox(annotations): + le = len(annotations) + boxes = [_get_bbox(ann) for ann in annotations] + mlb = sum(b[0] for b in boxes) / le + mtb = sum(b[1] for b in boxes) / le + mrb = sum(b[0] + b[2] for b in boxes) / le + mbb = sum(b[1] + b[3] for b in boxes) / le + return [mlb, mtb, mrb - mlb, mbb - mtb] + +def softmax(x): + return np.exp(x) / sum(np.exp(x)) + +def nms(segments, iou_thresh=0.5): + """ + Non-maxima suppression algorithm. + """ + + indices = np.argsort([b.attributes['score'] for b in segments]) + ious = np.array([[iou(a, b) for b in segments] for a in segments]) + + predictions = [] + while len(indices) != 0: + i = len(indices) - 1 + pred_idx = indices[i] + to_remove = [i] + predictions.append(segments[pred_idx]) + for i, box_idx in enumerate(indices[:i]): + if iou_thresh < ious[pred_idx, box_idx]: + to_remove.append(i) + indices = np.delete(indices, to_remove) + + return predictions + +def bbox_iou(a, b): + """ + IoU computations for simple cases with bounding boxes + """ + bbox_a = _get_bbox(a) + bbox_b = _get_bbox(b) + + aX, aY, aW, aH = bbox_a + bX, bY, bW, bH = bbox_b + in_right = min(aX + aW, bX + bW) + in_left = max(aX, bX) + in_top = max(aY, bY) + in_bottom = min(aY + aH, bY + bH) + + in_w = max(0, in_right - in_left) + in_h = max(0, in_bottom - in_top) + intersection = in_w * in_h + if not intersection: + return -1 + + a_area = aW * aH + b_area = bW * bH + union = a_area + b_area - intersection + return intersection / union + +def segment_iou(a, b): + """ + Generic IoU computation with masks, polygons, and boxes. + Returns -1 if no intersection, [0; 1] otherwise + """ + from pycocotools import mask as mask_utils + + a_bbox = a.get_bbox() + b_bbox = b.get_bbox() + + is_bbox = AnnotationType.bbox in [a.type, b.type] + if is_bbox: + a = [a_bbox] + b = [b_bbox] + else: + w = max(a_bbox[0] + a_bbox[2], b_bbox[0] + b_bbox[2]) + h = max(a_bbox[1] + a_bbox[3], b_bbox[1] + b_bbox[3]) + + def _to_rle(ann): + if ann.type == AnnotationType.polygon: + return mask_utils.frPyObjects([ann.points], h, w) + elif isinstance(ann, RleMask): + return [ann._rle] + elif ann.type == AnnotationType.mask: + return mask_utils.frPyObjects([mask_to_rle(ann.image)], h, w) + else: + raise TypeError("Unexpected arguments: %s, %s" % (a, b)) + a = _to_rle(a) + b = _to_rle(b) + return float(mask_utils.iou(a, b, [not is_bbox])) + +def PDJ(a, b, eps=None, ratio=0.05, bbox=None): + """ + Percentage of Detected Joints metric. + Counts the number of matching points. + """ + + assert eps is not None or ratio is not None + + p1 = np.array(a.points).reshape((-1, 2)) + p2 = np.array(b.points).reshape((-1, 2)) + if len(p1) != len(p2): + return 0 + + if not eps: + if bbox is None: + bbox = mean_bbox([a, b]) + + diag = (bbox[2] ** 2 + bbox[3] ** 2) ** 0.5 + eps = ratio * diag + + dists = np.linalg.norm(p1 - p2, axis=1) + return np.sum(dists < eps) / len(p1) + +def OKS(a, b, sigma=None, bbox=None, scale=None): + """ + Object Keypoint Similarity metric. + https://cocodataset.org/#keypoints-eval + """ + + p1 = np.array(a.points).reshape((-1, 2)) + p2 = np.array(b.points).reshape((-1, 2)) + if len(p1) != len(p2): + return 0 + + if not sigma: + sigma = 0.1 + else: + assert len(sigma) == len(p1) + + if not scale: + if bbox is None: + bbox = mean_bbox([a, b]) + scale = bbox[2] * bbox[3] + + dists = np.linalg.norm(p1 - p2, axis=1) + return np.sum(np.exp(-(dists ** 2) / (2 * scale * (2 * sigma) ** 2))) + +def smooth_line(points, segments): + assert 2 <= len(points) // 2 and len(points) % 2 == 0 + + if len(points) // 2 == segments: + return points + + points = list(points) + if len(points) == 2: + points.extend(points) + points = np.array(points).reshape((-1, 2)) + + lengths = np.linalg.norm(points[1:] - points[:-1], axis=1) + dists = [0] + for l in lengths: + dists.append(dists[-1] + l) + + step = dists[-1] / segments + + new_points = np.zeros((segments + 1, 2)) + new_points[0] = points[0] + + old_segment = 0 + for new_segment in range(1, segments + 1): + pos = new_segment * step + while dists[old_segment + 1] < pos and old_segment + 2 < len(dists): + old_segment += 1 + + segment_start = dists[old_segment] + segment_len = lengths[old_segment] + prev_p = points[old_segment] + next_p = points[old_segment + 1] + r = (pos - segment_start) / segment_len + + new_points[new_segment] = prev_p * (1 - r) + next_p * r + + return new_points, step diff --git a/datumaro/datumaro/util/attrs_util.py b/datumaro/datumaro/util/attrs_util.py new file mode 100644 index 00000000..15f0c318 --- /dev/null +++ b/datumaro/datumaro/util/attrs_util.py @@ -0,0 +1,34 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import attr + +_NOTSET = object() + +def not_empty(inst, attribute, x): + assert len(x) != 0, x + +def default_if_none(conv): + def validator(inst, attribute, value): + default = attribute.default + if value is None: + if callable(default): + value = default() + elif isinstance(default, attr.Factory): + value = default.factory() + else: + value = default + elif not isinstance(value, attribute.type or conv): + value = conv(value) + setattr(inst, attribute.name, value) + return validator + +def ensure_cls(c): + def converter(arg): + if isinstance(arg, c): + return arg + else: + return c(**arg) + return converter \ No newline at end of file diff --git a/datumaro/datumaro/util/image.py b/datumaro/datumaro/util/image.py index 3a7687a5..fc6a113c 100644 --- a/datumaro/datumaro/util/image.py +++ b/datumaro/datumaro/util/image.py @@ -5,11 +5,12 @@ # pylint: disable=unused-import +from enum import Enum from io import BytesIO import numpy as np +import os import os.path as osp -from enum import Enum _IMAGE_BACKENDS = Enum('_IMAGE_BACKENDS', ['cv2', 'PIL']) _IMAGE_BACKEND = None try: @@ -45,7 +46,16 @@ def load_image(path): assert image.shape[2] in {3, 4} return image -def save_image(path, image, **kwargs): +def save_image(path, image, create_dir=False, **kwargs): + # NOTE: Check destination path for existence + # OpenCV silently fails if target directory does not exist + dst_dir = osp.dirname(path) + if dst_dir: + if create_dir: + os.makedirs(dst_dir, exist_ok=True) + elif not osp.isdir(dst_dir): + raise FileNotFoundError("Directory does not exist: '%s'" % dst_dir) + if not kwargs: kwargs = {} @@ -205,10 +215,6 @@ class Image: def path(self): return self._path - @property - def filename(self): - return osp.basename(self._path) - @property def data(self): if callable(self._data): diff --git a/datumaro/datumaro/util/mask_tools.py b/datumaro/datumaro/util/mask_tools.py index a4eb8150..680093d9 100644 --- a/datumaro/datumaro/util/mask_tools.py +++ b/datumaro/datumaro/util/mask_tools.py @@ -9,6 +9,12 @@ from datumaro.util.image import lazy_image, load_image def generate_colormap(length=256): + """ + Generates colors using PASCAL VOC algorithm. + + Returns index -> (R, G, B) mapping. + """ + def get_bit(number, index): return (number >> index) & 1 diff --git a/datumaro/datumaro/util/os_util.py b/datumaro/datumaro/util/os_util.py new file mode 100644 index 00000000..b4d05e37 --- /dev/null +++ b/datumaro/datumaro/util/os_util.py @@ -0,0 +1,17 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import subprocess + + +def check_instruction_set(instruction): + return instruction == str.strip( + # Let's ignore a warning from bandit about using shell=True. + # In this case it isn't a security issue and we use some + # shell features like pipes. + subprocess.check_output( + 'lscpu | grep -o "%s" | head -1' % instruction, + shell=True).decode('utf-8') # nosec + ) \ No newline at end of file diff --git a/datumaro/datumaro/util/test_utils.py b/datumaro/datumaro/util/test_utils.py index 8600b621..f93a74ce 100644 --- a/datumaro/datumaro/util/test_utils.py +++ b/datumaro/datumaro/util/test_utils.py @@ -43,19 +43,6 @@ class TestDir(FileRemover): super().__init__(path, is_dir=True, ignore_errors=ignore_errors) -def ann_to_str(ann): - return vars(ann) - -def item_to_str(item): - return '\n'.join( - [ - '%s' % vars(item) - ] + [ - 'ann[%s]: %s' % (i, ann_to_str(a)) - for i, a in enumerate(item.annotations) - ] - ) - def compare_categories(test, expected, actual): test.assertEqual( sorted(expected, key=lambda t: t.value), @@ -78,7 +65,22 @@ def compare_categories(test, expected, actual): actual[AnnotationType.points].items, ) -def compare_datasets(test, expected, actual): +def _compare_annotations(expected, actual, ignored_attrs=None): + if not ignored_attrs: + return expected == actual + + a_attr = expected.attributes + b_attr = actual.attributes + + expected.attributes = {k:v for k,v in a_attr.items() if k not in ignored_attrs} + actual.attributes = {k:v for k,v in b_attr.items() if k not in ignored_attrs} + r = expected == actual + + expected.attributes = a_attr + actual.attributes = b_attr + return r + +def compare_datasets(test, expected, actual, ignored_attrs=None): compare_categories(test, expected.categories(), actual.categories()) test.assertEqual(sorted(expected.subsets()), sorted(actual.subsets())) @@ -87,14 +89,34 @@ def compare_datasets(test, expected, actual): item_b = find(actual, lambda x: x.id == item_a.id and \ x.subset == item_a.subset) test.assertFalse(item_b is None, item_a.id) + test.assertEqual(item_a.attributes, item_b.attributes) test.assertEqual(len(item_a.annotations), len(item_b.annotations)) for ann_a in item_a.annotations: # We might find few corresponding items, so check them all ann_b_matches = [x for x in item_b.annotations - if x.id == ann_a.id and \ - x.type == ann_a.type and x.group == ann_a.group] + if x.type == ann_a.type] test.assertFalse(len(ann_b_matches) == 0, 'ann id: %s' % ann_a.id) - ann_b = find(ann_b_matches, lambda x: x == ann_a) - test.assertEqual(ann_a, ann_b, 'ann: %s' % ann_to_str(ann_a)) - item_b.annotations.remove(ann_b) # avoid repeats \ No newline at end of file + ann_b = find(ann_b_matches, lambda x: + _compare_annotations(x, ann_a, ignored_attrs=ignored_attrs)) + if ann_b is None: + test.assertEqual(ann_a, ann_b, + 'ann %s, candidates %s' % (ann_a, ann_b_matches)) + item_b.annotations.remove(ann_b) # avoid repeats + +def compare_datasets_strict(test, expected, actual): + # Compares datasets for strong equality + + test.assertEqual(expected.categories(), actual.categories()) + + test.assertListEqual(sorted(expected.subsets()), sorted(actual.subsets())) + test.assertEqual(len(expected), len(actual)) + + for subset_name in expected.subsets(): + e_subset = expected.get_subset(subset_name) + a_subset = actual.get_subset(subset_name) + test.assertEqual(len(e_subset), len(a_subset)) + for idx, (item_a, item_b) in enumerate(zip(e_subset, a_subset)): + test.assertEqual(item_a, item_b, + '%s:\n%s\nvs.\n%s\n' % \ + (idx, item_a, item_b)) \ No newline at end of file diff --git a/datumaro/datumaro/util/tf_util.py b/datumaro/datumaro/util/tf_util.py index 00bf834a..f5d70090 100644 --- a/datumaro/datumaro/util/tf_util.py +++ b/datumaro/datumaro/util/tf_util.py @@ -3,17 +3,56 @@ # # SPDX-License-Identifier: MIT -def import_tf(): + +def check_import(): + # Workaround for checking import availability: + # Official TF builds include AVX instructions. Once we try to import, + # the program crashes. We raise an exception instead. + + import subprocess import sys - tf = sys.modules.get('tensorflow', None) - if tf is not None: + from .os_util import check_instruction_set + + result = subprocess.run([sys.executable, '-c', 'import tensorflow'], + timeout=60, + universal_newlines=True, # use text mode for output stream + stdout=subprocess.PIPE, stderr=subprocess.PIPE) # capture output + + if result.returncode != 0: + message = result.stderr + if not message: + message = "Can't import tensorflow. " \ + "Test process exit code: %s." % result.returncode + if not check_instruction_set('avx'): + # The process has probably crashed for AVX unavalability + message += " This is likely because your CPU does not " \ + "support AVX instructions, " \ + "which are required for tensorflow." + + raise ImportError(message) + +def import_tf(check=True): + import sys + + not_found = object() + tf = sys.modules.get('tensorflow', not_found) + if tf is None: + import tensorflow as tf # emit default error + elif tf is not not_found: return tf # Reduce output noise, https://stackoverflow.com/questions/38073432/how-to-suppress-verbose-tensorflow-logging import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' + if check: + try: + check_import() + except Exception: + sys.modules['tensorflow'] = None # prevent further import + raise + import tensorflow as tf try: diff --git a/datumaro/docs/design.md b/datumaro/docs/design.md index 7d89e8eb..528b2adf 100644 --- a/datumaro/docs/design.md +++ b/datumaro/docs/design.md @@ -49,6 +49,8 @@ Datumaro is: - Versioning (for images, annotations, subsets, sources etc., comparison) - Documentation generation - Provision of iterators for user code +- Dataset downloading +- Dataset generation - Dataset building (export in a specific format, indexation, statistics, documentation) - Dataset exporting to other formats - Dataset debugging (run inference, generate dataset slices, compute statistics) @@ -111,17 +113,17 @@ can be downloaded by user to be operated on with Datumaro CLI. - [ ] with TensorBoard - Calculation of statistics for datasets - - [ ] Pixel mean, std - - [ ] Object counts (detection scenario) - - [ ] Image-Class distribution (classification scenario) - - [ ] Pixel-Class distribution (segmentation scenario) - - [ ] Image clusters + - [x] Pixel mean, std + - [x] Object counts (detection scenario) + - [x] Image-Class distribution (classification scenario) + - [x] Pixel-Class distribution (segmentation scenario) + - [ ] Image similarity clusters - [ ] Custom statistics - Dataset building - [x] Composite dataset building - - [ ] Annotation remapping - - [ ] Subset splitting + - [x] Class remapping + - [x] Subset splitting - [x] Dataset filtering (`extract`) - [x] Dataset merging (`merge`) - [ ] Dataset item editing (`edit`) @@ -129,7 +131,7 @@ can be downloaded by user to be operated on with Datumaro CLI. - Dataset comparison (`diff`) - [x] Annotation-annotation comparison - [x] Annotation-inference comparison - - [ ] Annotation quality estimation (for CVAT) + - [x] Annotation quality estimation (for CVAT) - Provide a simple method to check annotation quality with a model and generate summary @@ -142,9 +144,9 @@ can be downloaded by user to be operated on with Datumaro CLI. - [x] Task export - [x] Datumaro project export - [x] Dataset export - - [ ] Original raw data (images, a video file) can be downloaded (exported) + - [x] Original raw data (images, a video file) can be downloaded (exported) together with annotations or just have links - on CVAT server (in the future support S3, etc) + on CVAT server (in future, support S3, etc) - [x] Be able to use local files instead of remote links - [ ] Specify cache directory - [x] Use case "annotate for model training" @@ -154,7 +156,7 @@ can be downloaded by user to be operated on with Datumaro CLI. - convert to a training format - train a DL model - [x] Use case "annotate - reannotate problematic images - merge" - - [ ] Use case "annotate and estimate quality" + - [x] Use case "annotate and estimate quality" - create a task - annotate - estimate quality of annotations diff --git a/datumaro/docs/user_manual.md b/datumaro/docs/user_manual.md index 65284fa1..e2e798e2 100644 --- a/datumaro/docs/user_manual.md +++ b/datumaro/docs/user_manual.md @@ -1,22 +1,28 @@ -# Quick start guide +# User manual ## Contents - [Installation](#installation) - [Interfaces](#interfaces) -- [Supported dataset formats and annotations](#formats-support) +- [Supported dataset formats and annotations](#supported-formats) - [Command line workflow](#command-line-workflow) +- [Command reference](#command-reference) + - [Convert datasets](#convert-datasets) - [Create a project](#create-project) - [Add and remove data](#add-and-remove-data) - [Import a project](#import-project) - [Extract a subproject](#extract-subproject) - - [Merge projects](#merge-project) + - [Update project (merge)](#update-project) + - [Merge projects](#merge-projects) - [Export a project](#export-project) - [Compare projects](#compare-projects) - - [Get project info](#get-project-info) + - [Obtaining project info](#get-project-info) + - [Obtaining project statistics](#get-project-statistics) - [Register a model](#register-model) - [Run inference](#run-inference) - [Run inference explanation](#explain-inference) + - [Transform a project](#transform-project) +- [Extending](#extending) - [Links](#links) ## Installation @@ -36,7 +42,7 @@ python -m virtualenv venv . venv/bin/activate ``` -Install Datumaro: +Install: ``` bash pip install 'git+https://github.com/opencv/cvat#egg=datumaro&subdirectory=datumaro' ``` @@ -68,57 +74,93 @@ As a python library: import datumaro ``` -## Formats support +## Supported Formats List of supported formats: -- COCO (`image_info`, `instances`, `person_keypoints`, `captions`, `labels`*) +- MS COCO (`image_info`, `instances`, `person_keypoints`, `captions`, `labels`*) - [Format specification](http://cocodataset.org/#format-data) + - [Dataset example](../tests/assets/coco_dataset) - `labels` are our extension - like `instances` with only `category_id` - PASCAL VOC (`classification`, `detection`, `segmentation` (class, instances), `action_classification`, `person_layout`) - [Format specification](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/index.html) + - [Dataset example](../tests/assets/voc_dataset) - YOLO (`bboxes`) - [Format specification](https://github.com/AlexeyAB/darknet#how-to-train-pascal-voc-data) + - [Dataset example](../tests/assets/yolo_dataset) - TF Detection API (`bboxes`, `masks`) - Format specifications: [bboxes](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/using_your_own_dataset.md), [masks](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/instance_segmentation.md) + - [Dataset example](../tests/assets/tf_detection_api_dataset) +- MOT sequences + - [Format specification](https://arxiv.org/pdf/1906.04567.pdf) + - [Dataset example](../tests/assets/mot_dataset) - CVAT - [Format specification](https://github.com/opencv/cvat/blob/develop/cvat/apps/documentation/xml_format.md) + - [Dataset example](../tests/assets/cvat_dataset) +- LabelMe + - [Format specification](http://labelme.csail.mit.edu/Release3.0) + - [Dataset example](../tests/assets/labelme_dataset) List of supported annotation types: - Labels - Bounding boxes - Polygons - Polylines +- (Segmentation) Masks - (Key-)Points - Captions -- Masks ## Command line workflow -> **Note**: command invocation syntax is subject to change, -> **always refer to command --help output** - -The key object is the Project. The Project is a combination of -a Project's own dataset, a number of external data sources and an environment. +The key object is a project, so most CLI commands operate on projects. However, there +are few commands operating on datasets directly. A project is a combination of +a project's own dataset, a number of external data sources and an environment. An empty Project can be created by `project create` command, an existing dataset can be imported with `project import` command. A typical way to obtain projects is to export tasks in CVAT UI. +If you want to interact with models, you need to add them to project first. + +## Command reference + +> **Note**: command invocation syntax is subject to change, +> **always refer to command --help output** + Available CLI commands: ![CLI design doc](images/cli_design.png) -If you want to interact with models, you need to add them to project first. +### Convert datasets + +This command allows to convert a dataset from one format into another. In fact, this +command is a combination of `project import` and `project export` and just provides a simpler +way to obtain the same result when no extra options is needed. A list of supported +formats can be found in the `--help` output of this command. + +Usage: + +``` bash +datum convert --help + +datum convert \ + -i \ + -if \ + -o \ + -f \ + -- [extra parameters for output format] +``` + +Example: convert a VOC-like dataset to a COCO-like one: + +``` bash +datum convert --input-format voc --input-path \ + --output-format coco +``` ### Import project This command creates a Project from an existing dataset. -Supported formats are listed in the command help. -In Datumaro dataset formats are supported by Extractors and Importers. -An Extractor produces a list of dataset items corresponding -to the dataset. An Importer creates a Project from the -data source location. It is possible to add a custom Extractor and Importer. -To do this, you need to put an Extractor and Importer implementation scripts to -`/.datumaro/extractors` and `/.datumaro/importers`. +Supported formats are listed in the command help. Check [extending tips](#extending) +for information on extra format support. Usage: @@ -178,7 +220,7 @@ datum project create -o my_dataset/ ### Add and remove data -A Project can be attached to a number of external Data Sources. Each Source +A Project can contain a number of external Data Sources. Each Data Source describes a way to produce dataset items. A Project combines dataset items from all the sources and its own dataset into one composite dataset. You can manage project sources by commands in the `source` command line context. @@ -195,11 +237,8 @@ is used in COCO format: ``` -In Datumaro dataset formats are supported by Extractors. -An Extractor produces a list of dataset items corresponding -to the dataset. It is possible to add a custom Extractor. -To do this, you need to put an Extractor -definition script to `/.datumaro/extractors`. +Supported formats are listed in the command help. Check [extending tips](#extending) +for information on extra format support. Usage: @@ -237,16 +276,16 @@ This command allows to create a sub-Project from a Project. The new project includes only items satisfying some condition. [XPath](https://devhints.io/xpath) is used as query format. -There are several filtering modes available ('-m/--mode' parameter). +There are several filtering modes available (`-m/--mode` parameter). Supported modes: -- 'i', 'items' -- 'a', 'annotations' -- 'i+a', 'a+i', 'items+annotations', 'annotations+items' +- `i`, `items` +- `a`, `annotations` +- `i+a`, `a+i`, `items+annotations`, `annotations+items` -When filtering annotations, use the 'items+annotations' +When filtering annotations, use the `items+annotations` mode to point that annotation-less dataset items should be removed. To select an annotation, write an XPath that -returns 'annotation' elements (see examples). +returns `annotation` elements (see examples). Usage: @@ -259,7 +298,7 @@ datum project extract \ -e '' ``` -Example: extract a dataset with only images which width < height +Example: extract a dataset with only images which `width` < `height` ``` bash datum project extract \ @@ -274,7 +313,7 @@ Example: extract a dataset with only large annotations of class `cat` and any no datum project extract \ -p test_project \ -o test_project-extract \ - --mode annotations -e '/item/annotation[(label="cat" and area > 999.5) or label!="person"]' + --mode annotations -e '/item/annotation[(label="cat" and area > 99.5) or label!="person"]' ``` Example: extract a dataset with only occluded annotations, remove empty images @@ -321,9 +360,9 @@ Item representations are available with `--dry-run` parameter: ``` -### Merge projects +### Update project -This command combines multiple Projects into one. +This command updates items in a project from another one (check [Merge Projects](#merge-projects) for complex merging). Usage: @@ -346,16 +385,40 @@ datum project merge \ second_project ``` +### Merge projects + +This command merges items from 2 or more projects and checks annotations for errors. + +Spatial annotations are compared by distance and intersected, labels and attributes +are selected by voting. +Merge conflicts, missing items and annotations, other errors are saved into a `.json` file. + +Usage: + +``` bash +datum merge --help + +datum merge +``` + +Example: merge 4 (partially-)intersecting projects, +- consider voting succeeded when there are 3+ same votes +- consider shapes intersecting when IoU >= 0.6 +- check annotation groups to have `person`, `hand`, `head` and `foot` (`?` for optional) + +``` bash +datum merge project1/ project2/ project3/ project4/ \ + --quorum 3 \ + -iou 0.6 \ + --groups 'person,hand?,head,foot?' +``` + ### Export project -This command exports a Project in some format. +This command exports a Project as a dataset in some format. -Supported formats are listed in the command help. -In Datumaro dataset formats are supported by Converters. -A Converter produces a dataset of a specific format -from dataset items. It is possible to add a custom Converter. -To do this, you need to put a Converter -definition script to /.datumaro/converters. +Supported formats are listed in the command help. Check [extending tips](#extending) +for information on extra format support. Usage: @@ -366,17 +429,17 @@ datum project export \ -p \ -o \ -f \ - [-- ] + -- [additional format parameters] ``` -Example: save project as VOC-like dataset, include images +Example: save project as VOC-like dataset, include images, convert images to `PNG` ``` bash datum project export \ -p test_project \ -o test_project-export \ -f voc \ - -- --save-images + -- --save-images --image-ext='.png' ``` ### Get project info @@ -398,7 +461,7 @@ Example: datum project info -p /test_project Project: - name: test_project2 + name: test_project location: /test_project Sources: source 'instances_minival2014': @@ -419,6 +482,282 @@ Dataset: labels: person, bicycle, car, motorcycle (and 76 more) ``` +### Get project statistics + +This command computes various project statistics, such as: +- image mean and std. dev. +- class and attribute balance +- mask pixel balance +- segment area distribution + +Usage: + +``` bash +datum project stats --help + +datum project stats \ + -p +``` + +Example: + +
+ +``` bash +datum project stats -p /test_project + +{ + "annotations": { + "labels": { + "attributes": { + "gender": { + "count": 358, + "distribution": { + "female": [ + 149, + 0.41620111731843573 + ], + "male": [ + 209, + 0.5837988826815642 + ] + }, + "values count": 2, + "values present": [ + "female", + "male" + ] + }, + "view": { + "count": 340, + "distribution": { + "__undefined__": [ + 4, + 0.011764705882352941 + ], + "front": [ + 54, + 0.1588235294117647 + ], + "left": [ + 14, + 0.041176470588235294 + ], + "rear": [ + 235, + 0.6911764705882353 + ], + "right": [ + 33, + 0.09705882352941177 + ] + }, + "values count": 5, + "values present": [ + "__undefined__", + "front", + "left", + "rear", + "right" + ] + } + }, + "count": 2038, + "distribution": { + "car": [ + 340, + 0.16683022571148184 + ], + "cyclist": [ + 194, + 0.09519136408243375 + ], + "head": [ + 354, + 0.17369970559371933 + ], + "ignore": [ + 100, + 0.04906771344455348 + ], + "left_hand": [ + 238, + 0.11678115799803729 + ], + "person": [ + 358, + 0.17566241413150147 + ], + "right_hand": [ + 77, + 0.037782139352306184 + ], + "road_arrows": [ + 326, + 0.15996074582924436 + ], + "traffic_sign": [ + 51, + 0.025024533856722278 + ] + } + }, + "segments": { + "area distribution": [ + { + "count": 1318, + "max": 11425.1, + "min": 0.0, + "percent": 0.9627465303140978 + }, + { + "count": 1, + "max": 22850.2, + "min": 11425.1, + "percent": 0.0007304601899196494 + }, + { + "count": 0, + "max": 34275.3, + "min": 22850.2, + "percent": 0.0 + }, + { + "count": 0, + "max": 45700.4, + "min": 34275.3, + "percent": 0.0 + }, + { + "count": 0, + "max": 57125.5, + "min": 45700.4, + "percent": 0.0 + }, + { + "count": 0, + "max": 68550.6, + "min": 57125.5, + "percent": 0.0 + }, + { + "count": 0, + "max": 79975.7, + "min": 68550.6, + "percent": 0.0 + }, + { + "count": 0, + "max": 91400.8, + "min": 79975.7, + "percent": 0.0 + }, + { + "count": 0, + "max": 102825.90000000001, + "min": 91400.8, + "percent": 0.0 + }, + { + "count": 50, + "max": 114251.0, + "min": 102825.90000000001, + "percent": 0.036523009495982466 + } + ], + "avg. area": 5411.624543462382, + "pixel distribution": { + "car": [ + 13655, + 0.0018431496518735067 + ], + "cyclist": [ + 939005, + 0.12674674030446592 + ], + "head": [ + 0, + 0.0 + ], + "ignore": [ + 5501200, + 0.7425510702956085 + ], + "left_hand": [ + 0, + 0.0 + ], + "person": [ + 954654, + 0.12885903974805205 + ], + "right_hand": [ + 0, + 0.0 + ], + "road_arrows": [ + 0, + 0.0 + ], + "traffic_sign": [ + 0, + 0.0 + ] + } + } + }, + "annotations by type": { + "bbox": { + "count": 548 + }, + "caption": { + "count": 0 + }, + "label": { + "count": 0 + }, + "mask": { + "count": 0 + }, + "points": { + "count": 669 + }, + "polygon": { + "count": 821 + }, + "polyline": { + "count": 0 + } + }, + "annotations count": 2038, + "dataset": { + "image mean": [ + 107.06903686941979, + 79.12831698580979, + 52.95829558185416 + ], + "image std": [ + 49.40237673503467, + 43.29600731496902, + 35.47373007603151 + ], + "images count": 100 + }, + "images count": 100, + "subsets": {}, + "unannotated images": [ + "img00051", + "img00052", + "img00053", + "img00054", + "img00055", + ], + "unannotated images count": 5 +} +``` + +
+ ### Register model Supported models: @@ -557,6 +896,85 @@ datum explain \ -s 1000 --progressive ``` +### Transform Project + +This command allows to modify images or annotations in a project all at once. + +``` bash +datum project transform --help + +datum project transform \ + -p \ + -o \ + -t \ + -- [extra transform options] +``` + +Example: split a dataset randomly to `train` and `test` subsets, ratio is 2:1 + +``` bash +datum project transform -t random_split -- --subset train:.67 --subset test:.33 +``` + +Example: convert polygons to masks, masks to boxes etc.: + +``` bash +datum project transform -t boxes_to_masks +datum project transform -t masks_to_polygons +datum project transform -t polygons_to_masks +datum project transform -t shapes_to_boxes +``` + +Example: remap dataset labels, `person` to `car` and `cat` to `dog`, keep `bus`, remove others + +``` bash +datum project transform -t remap_labels -- \ + -l person:car -l bus:bus -l cat:dog \ + --default delete +``` + +Example: rename dataset items by a regular expression +- Replace `pattern` with `replacement` +- Remove `frame_` from item ids + +``` bash +datum project transform -t rename -- -e '|pattern|replacement|' +datum project transform -t rename -- -e '|frame_(\d+)|\\1|' +``` + +## Extending + +There are few ways to extend and customize Datumaro behaviour, which is supported by plugins. +Check [our contribution guide](../CONTRIBUTING.md) for details on plugin implementation. +In general, a plugin is a Python code file. It must be put into a plugin directory: +- `/.datumaro/plugins` for project-specific plugins +- `/plugins` for global plugins + +### Dataset Formats + +Dataset reading is supported by Extractors and Importers. +An Extractor produces a list of dataset items corresponding +to the dataset. An Importer creates a project from the data source location. +It is possible to add custom Extractors and Importers. To do this, you need +to put an Extractor and Importer implementation scripts to a plugin directory. + +Dataset writing is supported by Converters. +A Converter produces a dataset of a specific format from dataset items. +It is possible to add custom Converters. To do this, you need to put a Converter +implementation script to a plugin directory. + +### Dataset Conversions ("Transforms") + +A Transform is a function for altering a dataset and producing a new one. It can update +dataset items, annotations, classes, and other properties. +A list of available transforms for dataset conversions can be extended by adding a Transform +implementation script into a plugin directory. + +### Model launchers + +A list of available launchers for model execution can be extended by adding a Launcher +implementation script into a plugin directory. + ## Links - [TensorFlow detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md) - [How to convert model to OpenVINO format](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_Object_Detection_API_Models.html) diff --git a/datumaro/requirements.txt b/datumaro/requirements.txt index c75978fc..ce583783 100644 --- a/datumaro/requirements.txt +++ b/datumaro/requirements.txt @@ -1,3 +1,4 @@ +attrs>=19.3.0 Cython>=0.27.3 # include before pycocotools defusedxml>=0.6.0 GitPython>=3.0.8 diff --git a/datumaro/setup.py b/datumaro/setup.py index 90c39ce4..4ebf1119 100644 --- a/datumaro/setup.py +++ b/datumaro/setup.py @@ -48,6 +48,7 @@ setuptools.setup( ], python_requires='>=3.5', install_requires=[ + 'attrs', 'defusedxml', 'GitPython', 'lxml', diff --git a/datumaro/tests/assets/coco_dataset/annotations/instances_val.json b/datumaro/tests/assets/coco_dataset/annotations/instances_val.json new file mode 100644 index 00000000..b5d9bd86 --- /dev/null +++ b/datumaro/tests/assets/coco_dataset/annotations/instances_val.json @@ -0,0 +1,59 @@ +{ + "licenses": [ + { + "name": "", + "id": 0, + "url": "" + } + ], + "info": { + "contributor": "", + "date_created": "", + "description": "", + "url": "", + "version": "", + "year": "" + }, + "categories": [ + { + "id": 1, + "name": "TEST", + "supercategory": "" + } + ], + "images": [ + { + "id": 1, + "width": 5, + "height": 10, + "file_name": "000000000001.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + } + ], + "annotations": [ + { + "id": 1, + "image_id": 1, + "category_id": 1, + "segmentation": [[0, 0, 1, 0, 1, 2, 0, 2]], + "area": 2, + "bbox": [0, 0, 1, 2], + "iscrowd": 0 + }, + { + "id": 2, + "image_id": 1, + "category_id": 1, + "segmentation": { + "counts": [0, 10, 5, 5, 5, 5, 0, 10, 10, 0], + "size": [10, 5] + }, + "area": 30, + "bbox": [0, 0, 10, 4], + "iscrowd": 1 + } + ] + } diff --git a/datumaro/tests/assets/coco_dataset/images/val/000000000001.jpg b/datumaro/tests/assets/coco_dataset/images/val/000000000001.jpg new file mode 100644 index 00000000..8bce84d3 Binary files /dev/null and b/datumaro/tests/assets/coco_dataset/images/val/000000000001.jpg differ diff --git a/datumaro/tests/assets/cvat_dataset/for_images/images/img0.jpg b/datumaro/tests/assets/cvat_dataset/for_images/images/img0.jpg new file mode 100644 index 00000000..9d28e0c1 Binary files /dev/null and b/datumaro/tests/assets/cvat_dataset/for_images/images/img0.jpg differ diff --git a/datumaro/tests/assets/cvat_dataset/for_images/images/img1.jpg b/datumaro/tests/assets/cvat_dataset/for_images/images/img1.jpg new file mode 100644 index 00000000..ee889d22 Binary files /dev/null and b/datumaro/tests/assets/cvat_dataset/for_images/images/img1.jpg differ diff --git a/datumaro/tests/assets/cvat_dataset/for_images/train.xml b/datumaro/tests/assets/cvat_dataset/for_images/train.xml new file mode 100644 index 00000000..02346484 --- /dev/null +++ b/datumaro/tests/assets/cvat_dataset/for_images/train.xml @@ -0,0 +1,45 @@ + + 1.1 + + + True + annotation + + + + + + + + + true + v3 + + + + + + + + diff --git a/datumaro/tests/assets/cvat_dataset/for_video/annotations.xml b/datumaro/tests/assets/cvat_dataset/for_video/annotations.xml new file mode 100644 index 00000000..5a68f811 --- /dev/null +++ b/datumaro/tests/assets/cvat_dataset/for_video/annotations.xml @@ -0,0 +1,92 @@ + + + 1.1 + + + 5 + v1 + 4 + interpolation + 2 + + 2020-04-23 08:57:24.614217+00:00 + 2020-04-23 09:04:48.168008+00:00 + 10 + 19 + step=3 + True + + + + + + + + 3 + 0 + 3 + http://localhost:7000/?id=3 + + + 4 + 2 + 3 + http://localhost:7000/?id=4 + + + + max + + + + + 25 + 20 + + + 2020-04-23 09:05:02.335612+00:00 + t.mp4 + + + + + + + + + + + + hgkf + + + jk + + + + + + + + + diff --git a/datumaro/tests/assets/cvat_dataset/for_video/images/frame_000010.png b/datumaro/tests/assets/cvat_dataset/for_video/images/frame_000010.png new file mode 100644 index 00000000..14996e0c Binary files /dev/null and b/datumaro/tests/assets/cvat_dataset/for_video/images/frame_000010.png differ diff --git a/datumaro/tests/assets/cvat_dataset/for_video/images/frame_000013.png b/datumaro/tests/assets/cvat_dataset/for_video/images/frame_000013.png new file mode 100644 index 00000000..14996e0c Binary files /dev/null and b/datumaro/tests/assets/cvat_dataset/for_video/images/frame_000013.png differ diff --git a/datumaro/tests/assets/mot_dataset/gt/gt.txt b/datumaro/tests/assets/mot_dataset/gt/gt.txt new file mode 100644 index 00000000..f4b7c0d4 --- /dev/null +++ b/datumaro/tests/assets/mot_dataset/gt/gt.txt @@ -0,0 +1 @@ +1,-1,0,4,4,8,1,3,1.0 diff --git a/datumaro/tests/assets/mot_dataset/gt/labels.txt b/datumaro/tests/assets/mot_dataset/gt/labels.txt new file mode 100644 index 00000000..6d9c393d --- /dev/null +++ b/datumaro/tests/assets/mot_dataset/gt/labels.txt @@ -0,0 +1,10 @@ +label_0 +label_1 +label_2 +label_3 +label_4 +label_5 +label_6 +label_7 +label_8 +label_9 \ No newline at end of file diff --git a/datumaro/tests/assets/mot_dataset/img1/000001.jpg b/datumaro/tests/assets/mot_dataset/img1/000001.jpg new file mode 100644 index 00000000..3588867b Binary files /dev/null and b/datumaro/tests/assets/mot_dataset/img1/000001.jpg differ diff --git a/datumaro/tests/assets/pytorch_launcher/__init__.py b/datumaro/tests/assets/pytorch_launcher/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/datumaro/tests/assets/pytorch_launcher/model_config.yml b/datumaro/tests/assets/pytorch_launcher/model_config.yml new file mode 100644 index 00000000..a3bef4fa --- /dev/null +++ b/datumaro/tests/assets/pytorch_launcher/model_config.yml @@ -0,0 +1,37 @@ +launcher: + framework: pytorch + module: samplenet.SampLeNet + python_path: '.' + checkpoint: 'samplenet.pth' + +# launcher returns raw result, so it should be converted +# to an appropriate representation with adapter +adapter: + type: classification + labels: + - label1 + - label2 + - label3 + - label4 + - label5 + - label6 + - label7 + - label8 + - label9 + - label10 + +# list of preprocessing, applied to each image during validation +# order of entries matters +preprocessing: + # resize input image to topology input size + # you may specify size to which image should be resized + # via dst_width, dst_height fields + - type: resize + size: 32 + # topology is trained on RGB images, but Datumaro reads in BGR + # so it must be converted to RGB + - type: bgr_to_rgb + # dataset mean and standard deviation + - type: normalization + mean: (125.307, 122.961, 113.8575) + std: (51.5865, 50.847, 51.255) \ No newline at end of file diff --git a/datumaro/tests/assets/pytorch_launcher/samplenet.pth b/datumaro/tests/assets/pytorch_launcher/samplenet.pth new file mode 100644 index 00000000..6c70368e Binary files /dev/null and b/datumaro/tests/assets/pytorch_launcher/samplenet.pth differ diff --git a/datumaro/tests/assets/pytorch_launcher/samplenet.py b/datumaro/tests/assets/pytorch_launcher/samplenet.py new file mode 100644 index 00000000..a742a650 --- /dev/null +++ b/datumaro/tests/assets/pytorch_launcher/samplenet.py @@ -0,0 +1,38 @@ +""" +Copyright (c) 2019 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import torch.nn as nn +import torch.nn.functional as F + + +class SampLeNet(nn.Module): + def __init__(self): + super(SampLeNet, self).__init__() + self.conv1 = nn.Conv2d(3, 6, 5) + self.pool = nn.MaxPool2d(2, 2) + self.conv2 = nn.Conv2d(6, 16, 5) + self.fc1 = nn.Linear(16 * 5 * 5, 120) + self.fc2 = nn.Linear(120, 84) + self.fc3 = nn.Linear(84, 10) + + def forward(self, x): + x = self.pool(F.relu(self.conv1(x))) + x = self.pool(F.relu(self.conv2(x))) + x = x.view(-1, 16 * 5 * 5) + x = F.relu(self.fc1(x)) + x = F.relu(self.fc2(x)) + x = self.fc3(x) + return x diff --git a/datumaro/tests/assets/tf_detection_api_dataset/label_map.pbtxt b/datumaro/tests/assets/tf_detection_api_dataset/label_map.pbtxt new file mode 100644 index 00000000..dbf2b339 --- /dev/null +++ b/datumaro/tests/assets/tf_detection_api_dataset/label_map.pbtxt @@ -0,0 +1,50 @@ +item { + id: 1 + name: 'label_0' +} + +item { + id: 2 + name: 'label_1' +} + +item { + id: 3 + name: 'label_2' +} + +item { + id: 4 + name: 'label_3' +} + +item { + id: 5 + name: 'label_4' +} + +item { + id: 6 + name: 'label_5' +} + +item { + id: 7 + name: 'label_6' +} + +item { + id: 8 + name: 'label_7' +} + +item { + id: 9 + name: 'label_8' +} + +item { + id: 10 + name: 'label_9' +} + diff --git a/datumaro/tests/assets/tf_detection_api_dataset/test.tfrecord b/datumaro/tests/assets/tf_detection_api_dataset/test.tfrecord new file mode 100644 index 00000000..81dafa70 Binary files /dev/null and b/datumaro/tests/assets/tf_detection_api_dataset/test.tfrecord differ diff --git a/datumaro/tests/assets/tf_detection_api_dataset/train.tfrecord b/datumaro/tests/assets/tf_detection_api_dataset/train.tfrecord new file mode 100644 index 00000000..3ca38331 Binary files /dev/null and b/datumaro/tests/assets/tf_detection_api_dataset/train.tfrecord differ diff --git a/datumaro/tests/assets/tf_detection_api_dataset/val.tfrecord b/datumaro/tests/assets/tf_detection_api_dataset/val.tfrecord new file mode 100644 index 00000000..34fa9ce1 Binary files /dev/null and b/datumaro/tests/assets/tf_detection_api_dataset/val.tfrecord differ diff --git a/datumaro/tests/assets/voc_dataset/Annotations/2007_000001.xml b/datumaro/tests/assets/voc_dataset/Annotations/2007_000001.xml new file mode 100644 index 00000000..4f1e25a2 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/Annotations/2007_000001.xml @@ -0,0 +1,54 @@ + + + VOC2007 + 2007_000001.jpg + + 10 + 20 + 3 + + 1 + + cat + Unspecified + 1 + 0 + + 1 + 2 + 3 + 4 + + + + person + + 4 + 5 + 6 + 7 + + + head + + 5.5 + 6 + 7.5 + 8 + + + + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + + + diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Action/test.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Action/test.txt new file mode 100644 index 00000000..c9fdc251 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Action/test.txt @@ -0,0 +1 @@ +2007_000002 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Action/train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Action/train.txt new file mode 100644 index 00000000..640b0d53 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Action/train.txt @@ -0,0 +1 @@ +2007_000001 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Layout/test.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Layout/test.txt new file mode 100644 index 00000000..c9fdc251 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Layout/test.txt @@ -0,0 +1 @@ +2007_000002 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Layout/train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Layout/train.txt new file mode 100644 index 00000000..640b0d53 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Layout/train.txt @@ -0,0 +1 @@ +2007_000001 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/aeroplane_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/aeroplane_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/aeroplane_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/background_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/background_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/background_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/bicycle_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bicycle_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bicycle_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/bird_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bird_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bird_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/boat_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/boat_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/boat_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/bottle_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bottle_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bottle_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/bus_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bus_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/bus_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/car_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/car_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/car_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/cat_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/cat_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/cat_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/chair_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/chair_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/chair_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/cow_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/cow_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/cow_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/diningtable_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/diningtable_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/diningtable_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/dog_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/dog_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/dog_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/horse_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/horse_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/horse_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/ignored_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/ignored_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/ignored_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/motorbike_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/motorbike_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/motorbike_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/person_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/person_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/person_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/pottedplant_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/pottedplant_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/pottedplant_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/sheep_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/sheep_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/sheep_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/sofa_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/sofa_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/sofa_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/test.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/test.txt new file mode 100644 index 00000000..c9fdc251 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/test.txt @@ -0,0 +1 @@ +2007_000002 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/train.txt new file mode 100644 index 00000000..640b0d53 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/train.txt @@ -0,0 +1 @@ +2007_000001 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/train_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/train_train.txt new file mode 100644 index 00000000..a3decd42 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/train_train.txt @@ -0,0 +1 @@ +2007_000001 1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Main/tvmonitor_train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Main/tvmonitor_train.txt new file mode 100644 index 00000000..d4385b69 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Main/tvmonitor_train.txt @@ -0,0 +1 @@ +2007_000001 -1 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Segmentation/test.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Segmentation/test.txt new file mode 100644 index 00000000..c9fdc251 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Segmentation/test.txt @@ -0,0 +1 @@ +2007_000002 diff --git a/datumaro/tests/assets/voc_dataset/ImageSets/Segmentation/train.txt b/datumaro/tests/assets/voc_dataset/ImageSets/Segmentation/train.txt new file mode 100644 index 00000000..640b0d53 --- /dev/null +++ b/datumaro/tests/assets/voc_dataset/ImageSets/Segmentation/train.txt @@ -0,0 +1 @@ +2007_000001 diff --git a/datumaro/tests/assets/voc_dataset/JPEGImages/2007_000002.jpg b/datumaro/tests/assets/voc_dataset/JPEGImages/2007_000002.jpg new file mode 100644 index 00000000..3c81296b Binary files /dev/null and b/datumaro/tests/assets/voc_dataset/JPEGImages/2007_000002.jpg differ diff --git a/datumaro/tests/assets/voc_dataset/SegmentationClass/2007_000001.png b/datumaro/tests/assets/voc_dataset/SegmentationClass/2007_000001.png new file mode 100644 index 00000000..0b920514 Binary files /dev/null and b/datumaro/tests/assets/voc_dataset/SegmentationClass/2007_000001.png differ diff --git a/datumaro/tests/assets/voc_dataset/SegmentationObject/2007_000001.png b/datumaro/tests/assets/voc_dataset/SegmentationObject/2007_000001.png new file mode 100644 index 00000000..ebbeee61 Binary files /dev/null and b/datumaro/tests/assets/voc_dataset/SegmentationObject/2007_000001.png differ diff --git a/datumaro/tests/assets/yolo_dataset/obj.data b/datumaro/tests/assets/yolo_dataset/obj.data new file mode 100644 index 00000000..16ca4090 --- /dev/null +++ b/datumaro/tests/assets/yolo_dataset/obj.data @@ -0,0 +1,4 @@ +classes = 10 +train = data/train.txt +names = data/obj.names +backup = backup/ diff --git a/datumaro/tests/assets/yolo_dataset/obj.names b/datumaro/tests/assets/yolo_dataset/obj.names new file mode 100644 index 00000000..b24c644d --- /dev/null +++ b/datumaro/tests/assets/yolo_dataset/obj.names @@ -0,0 +1,10 @@ +label_0 +label_1 +label_2 +label_3 +label_4 +label_5 +label_6 +label_7 +label_8 +label_9 diff --git a/datumaro/tests/assets/yolo_dataset/obj_train_data/1.jpg b/datumaro/tests/assets/yolo_dataset/obj_train_data/1.jpg new file mode 100644 index 00000000..8689b956 Binary files /dev/null and b/datumaro/tests/assets/yolo_dataset/obj_train_data/1.jpg differ diff --git a/datumaro/tests/assets/yolo_dataset/obj_train_data/1.txt b/datumaro/tests/assets/yolo_dataset/obj_train_data/1.txt new file mode 100644 index 00000000..1f507909 --- /dev/null +++ b/datumaro/tests/assets/yolo_dataset/obj_train_data/1.txt @@ -0,0 +1,2 @@ +2 0.133333 0.300000 0.266667 0.200000 +4 0.266667 0.450000 0.133333 0.300000 diff --git a/datumaro/tests/assets/yolo_dataset/train.txt b/datumaro/tests/assets/yolo_dataset/train.txt new file mode 100644 index 00000000..f55beb73 --- /dev/null +++ b/datumaro/tests/assets/yolo_dataset/train.txt @@ -0,0 +1 @@ +data/obj_train_data/1.jpg diff --git a/datumaro/tests/test_coco_format.py b/datumaro/tests/test_coco_format.py index 19ae0805..131284be 100644 --- a/datumaro/tests/test_coco_format.py +++ b/datumaro/tests/test_coco_format.py @@ -1,12 +1,11 @@ -import json +from functools import partial import numpy as np -import os import os.path as osp from unittest import TestCase -from datumaro.components.project import Project -from datumaro.components.extractor import (Extractor, DatasetItem, +from datumaro.components.project import Project, Dataset +from datumaro.components.extractor import (DatasetItem, AnnotationType, Label, Mask, Points, Polygon, Bbox, Caption, LabelCategories, PointsCategories ) @@ -19,128 +18,36 @@ from datumaro.plugins.coco_format.converter import ( CocoLabelsConverter, ) from datumaro.plugins.coco_format.importer import CocoImporter -from datumaro.util.image import save_image, Image +from datumaro.util.image import Image from datumaro.util.test_utils import TestDir, compare_datasets -class CocoImporterTest(TestCase): - @staticmethod - def generate_annotation(): - annotation = { - 'licenses': [], - 'info': {}, - 'categories': [], - 'images': [], - 'annotations': [], - } - annotation['licenses'].append({ - 'name': '', - 'id': 0, - 'url': '', - }) - annotation['info'] = { - 'contributor': '', - 'date_created': '', - 'description': '', - 'url': '', - 'version': '', - 'year': '', - } - annotation['licenses'].append({ - 'name': '', - 'id': 0, - 'url': '', - }) - annotation['categories'].append({ - 'id': 1, - 'name': 'TEST', - 'supercategory': '', - }) - annotation['images'].append({ - "id": 1, - "width": 5, - "height": 10, - "file_name": '000000000001.jpg', - "license": 0, - "flickr_url": '', - "coco_url": '', - "date_captured": 0, - }) - annotation['annotations'].append({ - "id": 1, - "image_id": 1, - "category_id": 1, - "segmentation": [[0, 0, 1, 0, 1, 2, 0, 2]], - "area": 2, - "bbox": [0, 0, 1, 2], - "iscrowd": 0, - }) - annotation['annotations'].append({ - "id": 2, - "image_id": 1, - "category_id": 1, - "segmentation": { - "counts": [ - 0, 10, - 5, 5, - 5, 5, - 0, 10, - 10, 0], - "size": [10, 5]}, - "area": 30, - "bbox": [0, 0, 10, 4], - "iscrowd": 1, - }) - return annotation - - def COCO_dataset_generate(self, path): - img_dir = osp.join(path, 'images', 'val') - ann_dir = osp.join(path, 'annotations') - os.makedirs(img_dir) - os.makedirs(ann_dir) - - image = np.ones((10, 5, 3)) - save_image(osp.join(img_dir, '000000000001.jpg'), image) - - annotation = self.generate_annotation() - - with open(osp.join(ann_dir, 'instances_val.json'), 'w') as outfile: - json.dump(annotation, outfile) +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'coco_dataset') +class CocoImporterTest(TestCase): def test_can_import(self): - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.ones((10, 5, 3)), subset='val', - annotations=[ - Polygon([0, 0, 1, 0, 1, 2, 0, 2], label=0, - id=1, group=1, attributes={'is_crowd': False}), - Mask(np.array( - [[1, 0, 0, 1, 0]] * 5 + - [[1, 1, 1, 1, 0]] * 5 - ), label=0, - id=2, group=2, attributes={'is_crowd': True}), - ] - ), - ]) - - def categories(self): - label_cat = LabelCategories() - label_cat.add('TEST') - return { AnnotationType.label: label_cat } - - with TestDir() as test_dir: - self.COCO_dataset_generate(test_dir) + expected_dataset = Dataset.from_iterable([ + DatasetItem(id='000000000001', image=np.ones((10, 5, 3)), + subset='val', attributes={'id': 1}, + annotations=[ + Polygon([0, 0, 1, 0, 1, 2, 0, 2], label=0, + id=1, group=1, attributes={'is_crowd': False}), + Mask(np.array( + [[1, 0, 0, 1, 0]] * 5 + + [[1, 1, 1, 1, 0]] * 5 + ), label=0, + id=2, group=2, attributes={'is_crowd': True}), + ] + ), + ], categories=['TEST',]) - dataset = Project.import_from(test_dir, 'coco').make_dataset() + dataset = Project.import_from(DUMMY_DATASET_DIR, 'coco') \ + .make_dataset() - compare_datasets(self, DstExtractor(), dataset) + compare_datasets(self, expected_dataset, dataset) def test_can_detect(self): - with TestDir() as test_dir: - self.COCO_dataset_generate(test_dir) - - self.assertTrue(CocoImporter.detect(test_dir)) + self.assertTrue(CocoImporter.detect(DUMMY_DATASET_DIR)) class CocoConverterTest(TestCase): def _test_save_and_load(self, source_dataset, converter, test_dir, @@ -157,491 +64,416 @@ class CocoConverterTest(TestCase): compare_datasets(self, expected=target_dataset, actual=parsed_dataset) def test_can_save_and_load_captions(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - annotations=[ - Caption('hello', id=1, group=1), - Caption('world', id=2, group=2), - ]), - DatasetItem(id=2, subset='train', - annotations=[ - Caption('test', id=3, group=3), - ]), - - DatasetItem(id=3, subset='val', - annotations=[ - Caption('word', id=1, group=1), - ] - ), - ]) + expected_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + annotations=[ + Caption('hello', id=1, group=1), + Caption('world', id=2, group=2), + ], attributes={'id': 1}), + DatasetItem(id=2, subset='train', + annotations=[ + Caption('test', id=3, group=3), + ], attributes={'id': 2}), + + DatasetItem(id=3, subset='val', + annotations=[ + Caption('word', id=1, group=1), + ], attributes={'id': 1}), + ]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoCaptionsConverter(), test_dir) + self._test_save_and_load(expected_dataset, + CocoCaptionsConverter.convert, test_dir) def test_can_save_and_load_instances(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add(str(i)) - categories = { AnnotationType.label: label_categories } - - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', image=np.ones((4, 4, 3)), - annotations=[ - # Bbox + single polygon - Bbox(0, 1, 2, 2, - label=2, group=1, id=1, - attributes={ 'is_crowd': False }), - Polygon([0, 1, 2, 1, 2, 3, 0, 3], - attributes={ 'is_crowd': False }, - label=2, group=1, id=1), - ]), - DatasetItem(id=2, subset='train', image=np.ones((4, 4, 3)), - annotations=[ - # Mask + bbox - Mask(np.array([ - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 1, 1], - [0, 0, 0, 0]], - ), - attributes={ 'is_crowd': True }, - label=4, group=3, id=3), - Bbox(1, 0, 2, 2, label=4, group=3, id=3, - attributes={ 'is_crowd': True }), - ]), - - DatasetItem(id=3, subset='val', image=np.ones((4, 4, 3)), - annotations=[ - # Bbox + mask - Bbox(0, 1, 2, 2, label=4, group=3, id=3, - attributes={ 'is_crowd': True }), - Mask(np.array([ - [0, 0, 0, 0], - [1, 1, 1, 0], - [1, 1, 0, 0], - [0, 0, 0, 0]], - ), - attributes={ 'is_crowd': True }, - label=4, group=3, id=3), - ]), - ]) - - def categories(self): - return categories - - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', image=np.ones((4, 4, 3)), - annotations=[ - Polygon([0, 1, 2, 1, 2, 3, 0, 3], - attributes={ 'is_crowd': False }, - label=2, group=1, id=1), - ]), - DatasetItem(id=2, subset='train', image=np.ones((4, 4, 3)), - annotations=[ - Mask(np.array([ - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 1, 1], - [0, 0, 0, 0]], - ), - attributes={ 'is_crowd': True }, - label=4, group=3, id=3), - ]), - - DatasetItem(id=3, subset='val', image=np.ones((4, 4, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 0, 0], - [1, 1, 1, 0], - [1, 1, 0, 0], - [0, 0, 0, 0]], - ), - attributes={ 'is_crowd': True }, - label=4, group=3, id=3), - ]), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + # Bbox + single polygon + Bbox(0, 1, 2, 2, + label=2, group=1, id=1, + attributes={ 'is_crowd': False }), + Polygon([0, 1, 2, 1, 2, 3, 0, 3], + attributes={ 'is_crowd': False }, + label=2, group=1, id=1), + ], attributes={'id': 1}), + DatasetItem(id=2, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + # Mask + bbox + Mask(np.array([ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 1, 1], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + Bbox(1, 0, 2, 2, label=4, group=3, id=3, + attributes={ 'is_crowd': True }), + ], attributes={'id': 2}), + + DatasetItem(id=3, subset='val', image=np.ones((4, 4, 3)), + annotations=[ + # Bbox + mask + Bbox(0, 1, 2, 2, label=4, group=3, id=3, + attributes={ 'is_crowd': True }), + Mask(np.array([ + [0, 0, 0, 0], + [1, 1, 1, 0], + [1, 1, 0, 0], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + ], attributes={'id': 1}), + ], categories=[str(i) for i in range(10)]) + + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + Polygon([0, 1, 2, 1, 2, 3, 0, 3], + attributes={ 'is_crowd': False }, + label=2, group=1, id=1), + ], attributes={'id': 1}), + DatasetItem(id=2, subset='train', image=np.ones((4, 4, 3)), + annotations=[ + Mask(np.array([ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 1, 1], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + ], attributes={'id': 2}), - def categories(self): - return categories + DatasetItem(id=3, subset='val', image=np.ones((4, 4, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 0, 0], + [1, 1, 1, 0], + [1, 1, 0, 0], + [0, 0, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=4, group=3, id=3), + ], attributes={'id': 1}) + ], categories=[str(i) for i in range(10)]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoInstancesConverter(), test_dir, - target_dataset=DstExtractor()) + self._test_save_and_load(source_dataset, + CocoInstancesConverter.convert, test_dir, + target_dataset=target_dataset) def test_can_merge_polygons_on_loading(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add(str(i)) - categories = { AnnotationType.label: label_categories } - - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((6, 10, 3)), - annotations=[ - Polygon([0, 0, 4, 0, 4, 4], - label=3, id=4, group=4), - Polygon([5, 0, 9, 0, 5, 5], - label=3, id=4, group=4), - ] - ), - ]) - - def categories(self): - return categories + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((6, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=3, id=4, group=4), + Polygon([5, 0, 9, 0, 5, 5], + label=3, id=4, group=4), + ] + ), + ], categories=[str(i) for i in range(10)]) - class TargetExtractor(TestExtractor): - def __iter__(self): - items = list(super().__iter__()) - items[0]._annotations = [ + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((6, 10, 3)), + annotations=[ Mask(np.array([ - [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], - # only internal fragment (without the border), - # but not everywhere... - ), - label=3, id=4, group=4, - attributes={ 'is_crowd': False }), - ] - return iter(items) + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + # only internal fragment (without the border), + # but not everywhere... + ), + label=3, id=4, group=4, + attributes={ 'is_crowd': False }), + ], attributes={'id': 1} + ), + ], categories=[str(i) for i in range(10)]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoInstancesConverter(), test_dir, + self._test_save_and_load(source_dataset, + CocoInstancesConverter.convert, test_dir, importer_args={'merge_instance_polygons': True}, - target_dataset=TargetExtractor()) + target_dataset=target_dataset) def test_can_crop_covered_segments(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add(str(i)) - - class SrcTestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 1, 1, 1], - [0, 0, 1, 1, 1], - [1, 1, 0, 1, 1], - [1, 1, 1, 0, 0], - [1, 1, 1, 0, 0]], - ), - label=2, id=1, z_order=0), - Polygon([1, 1, 4, 1, 4, 4, 1, 4], - label=1, id=2, z_order=1), - ] - ), - ]) - - def categories(self): - return { AnnotationType.label: label_categories } - - class DstTestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 1, 1, 1], - [0, 0, 0, 0, 1], - [1, 0, 0, 0, 1], - [1, 0, 0, 0, 0], - [1, 1, 1, 0, 0]], - ), - attributes={ 'is_crowd': True }, - label=2, id=1, group=1), - - Polygon([1, 1, 4, 1, 4, 4, 1, 4], - label=1, id=2, group=2, - attributes={ 'is_crowd': False }), - ] - ), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 1, 1, 1], + [1, 1, 0, 1, 1], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], + ), + label=2, id=1, z_order=0), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + label=1, id=2, z_order=1), + ] + ), + ], categories=[str(i) for i in range(10)]) - def categories(self): - return { AnnotationType.label: label_categories } + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), + attributes={ 'is_crowd': True }, + label=2, id=1, group=1), + + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + label=1, id=2, group=2, + attributes={ 'is_crowd': False }), + ], attributes={'id': 1} + ), + ], categories=[str(i) for i in range(10)]) with TestDir() as test_dir: - self._test_save_and_load(SrcTestExtractor(), - CocoInstancesConverter(crop_covered=True), test_dir, - target_dataset=DstTestExtractor()) + self._test_save_and_load(source_dataset, + partial(CocoInstancesConverter.convert, crop_covered=True), + test_dir, target_dataset=target_dataset) def test_can_convert_polygons_to_mask(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add(str(i)) - - class SrcTestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((6, 10, 3)), - annotations=[ - Polygon([0, 0, 4, 0, 4, 4], - label=3, id=4, group=4), - Polygon([5, 0, 9, 0, 5, 5], - label=3, id=4, group=4), - ] - ), - ]) - - def categories(self): - return { AnnotationType.label: label_categories } - - class DstTestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((6, 10, 3)), - annotations=[ - Mask(np.array([ - [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], - # only internal fragment (without the border), - # but not everywhere... - ), - attributes={ 'is_crowd': True }, - label=3, id=4, group=4), - ] - ), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((6, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=3, id=4, group=4), + Polygon([5, 0, 9, 0, 5, 5], + label=3, id=4, group=4), + ] + ), + ], categories=[str(i) for i in range(10)]) - def categories(self): - return { AnnotationType.label: label_categories } + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((6, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + # only internal fragment (without the border), + # but not everywhere... + ), + attributes={ 'is_crowd': True }, + label=3, id=4, group=4), + ], attributes={'id': 1} + ), + ], categories=[str(i) for i in range(10)]) with TestDir() as test_dir: - self._test_save_and_load(SrcTestExtractor(), - CocoInstancesConverter(segmentation_mode='mask'), test_dir, - target_dataset=DstTestExtractor()) + self._test_save_and_load(source_dataset, + partial(CocoInstancesConverter.convert, segmentation_mode='mask'), + test_dir, target_dataset=target_dataset) def test_can_convert_masks_to_polygons(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add(str(i)) - - class SrcTestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 10, 3)), - annotations=[ - Mask(np.array([ - [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]), - label=3, id=4, group=4), - ] - ), - ]) - - def categories(self): - return { AnnotationType.label: label_categories } - - class DstTestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 10, 3)), - annotations=[ - Polygon( - [3.0, 2.5, 1.0, 0.0, 3.5, 0.0, 3.0, 2.5], - label=3, id=4, group=4, - attributes={ 'is_crowd': False }), - Polygon( - [5.0, 3.5, 4.5, 0.0, 8.0, 0.0, 5.0, 3.5], - label=3, id=4, group=4, - attributes={ 'is_crowd': False }), - ] - ), - ]) - - def categories(self): - return { AnnotationType.label: label_categories } + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 1, 1, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), + label=3, id=4, group=4), + ] + ), + ], categories=[str(i) for i in range(10)]) + + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Polygon( + [3.0, 2.5, 1.0, 0.0, 3.5, 0.0, 3.0, 2.5], + label=3, id=4, group=4, + attributes={ 'is_crowd': False }), + Polygon( + [5.0, 3.5, 4.5, 0.0, 8.0, 0.0, 5.0, 3.5], + label=3, id=4, group=4, + attributes={ 'is_crowd': False }), + ], attributes={'id': 1} + ), + ], categories=[str(i) for i in range(10)]) with TestDir() as test_dir: - self._test_save_and_load(SrcTestExtractor(), - CocoInstancesConverter(segmentation_mode='polygons'), test_dir, - target_dataset=DstTestExtractor()) + self._test_save_and_load(source_dataset, + partial(CocoInstancesConverter.convert, segmentation_mode='polygons'), + test_dir, + target_dataset=target_dataset) def test_can_save_and_load_images(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train'), - DatasetItem(id=2, subset='train'), + expected_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', attributes={'id': 1}), + DatasetItem(id=2, subset='train', attributes={'id': 2}), - DatasetItem(id=2, subset='val'), - DatasetItem(id=3, subset='val'), - DatasetItem(id=4, subset='val'), + DatasetItem(id=2, subset='val', attributes={'id': 2}), + DatasetItem(id=3, subset='val', attributes={'id': 3}), + DatasetItem(id=4, subset='val', attributes={'id': 4}), - DatasetItem(id=5, subset='test'), - ]) + DatasetItem(id=5, subset='test', attributes={'id': 1}), + ]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoImageInfoConverter(), test_dir) + self._test_save_and_load(expected_dataset, + CocoImageInfoConverter.convert, test_dir) def test_can_save_and_load_labels(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - annotations=[ - Label(4, id=1, group=1), - Label(9, id=2, group=2), - ]), - DatasetItem(id=2, subset='train', - annotations=[ - Label(4, id=4, group=4), - ]), - - DatasetItem(id=3, subset='val', - annotations=[ - Label(2, id=1, group=1), - ]), - ]) - - def categories(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add(str(i)) - return { - AnnotationType.label: label_categories, - } + expected_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + annotations=[ + Label(4, id=1, group=1), + Label(9, id=2, group=2), + ], attributes={'id': 1}), + ], categories=[str(i) for i in range(10)]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoLabelsConverter(), test_dir) + self._test_save_and_load(expected_dataset, + CocoLabelsConverter.convert, test_dir) def test_can_save_and_load_keypoints(self): - label_categories = LabelCategories() - points_categories = PointsCategories() - for i in range(10): - label_categories.add(str(i)) - points_categories.add(i, joints=[[0, 1], [1, 2]]) - categories = { - AnnotationType.label: label_categories, - AnnotationType.points: points_categories, - } - - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', image=np.zeros((5, 5, 3)), - annotations=[ - # Full instance annotations: polygon + keypoints - Points([0, 0, 0, 2, 4, 1], [0, 1, 2], - label=3, group=1, id=1), - Polygon([0, 0, 4, 0, 4, 4], - label=3, group=1, id=1), - - # Full instance annotations: bbox + keypoints - Points([1, 2, 3, 4, 2, 3], group=2, id=2), - Bbox(1, 2, 2, 2, group=2, id=2), - ]), - DatasetItem(id=2, subset='train', image=np.zeros((5, 4, 3)), - annotations=[ - # Solitary keypoints - Points([1, 2, 0, 2, 4, 1], label=5, id=3), + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', image=np.zeros((5, 5, 3)), + annotations=[ + # Full instance annotations: polygon + keypoints + Points([0, 0, 0, 2, 4, 1], [0, 1, 2], + label=3, group=1, id=1), + Polygon([0, 0, 4, 0, 4, 4], + label=3, group=1, id=1), + + # Full instance annotations: bbox + keypoints + Points([1, 2, 3, 4, 2, 3], group=2, id=2), + Bbox(1, 2, 2, 2, group=2, id=2), + + # Solitary keypoints + Points([1, 2, 0, 2, 4, 1], label=5, id=3), + + # Some other solitary annotations (bug #1387) + Polygon([0, 0, 4, 0, 4, 4], label=3, id=4), + + # Solitary keypoints with no label + Points([0, 0, 1, 2, 3, 4], [0, 1, 2], id=5), + ]), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + str(i) for i in range(10)), + AnnotationType.points: PointsCategories.from_iterable( + (i, None, [[0, 1], [1, 2]]) for i in range(10) + ), + }) + + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', image=np.zeros((5, 5, 3)), + annotations=[ + Points([0, 0, 0, 2, 4, 1], [0, 1, 2], + label=3, group=1, id=1, + attributes={'is_crowd': False}), + Polygon([0, 0, 4, 0, 4, 4], + label=3, group=1, id=1, + attributes={'is_crowd': False}), + + Points([1, 2, 3, 4, 2, 3], + group=2, id=2, + attributes={'is_crowd': False}), + Bbox(1, 2, 2, 2, + group=2, id=2, + attributes={'is_crowd': False}), + + Points([1, 2, 0, 2, 4, 1], + label=5, group=3, id=3, + attributes={'is_crowd': False}), + Bbox(0, 1, 4, 1, + label=5, group=3, id=3, + attributes={'is_crowd': False}), + + Points([0, 0, 1, 2, 3, 4], [0, 1, 2], + group=5, id=5, + attributes={'is_crowd': False}), + Bbox(1, 2, 2, 2, + group=5, id=5, + attributes={'is_crowd': False}), + ], attributes={'id': 1}), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + str(i) for i in range(10)), + AnnotationType.points: PointsCategories.from_iterable( + (i, None, [[0, 1], [1, 2]]) for i in range(10) + ), + }) - # Some other solitary annotations (bug #1387) - Polygon([0, 0, 4, 0, 4, 4], label=3, id=4), - ]), + with TestDir() as test_dir: + self._test_save_and_load(source_dataset, + CocoPersonKeypointsConverter.convert, test_dir, + target_dataset=target_dataset) - DatasetItem(id=3, subset='val', - annotations=[ - # Solitary keypoints with no label - Points([0, 0, 1, 2, 3, 4], [0, 1, 2], id=3), - ]), - ]) - - def categories(self): - return categories - - class DstTestExtractor(TestExtractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', image=np.zeros((5, 5, 3)), - annotations=[ - Points([0, 0, 0, 2, 4, 1], [0, 1, 2], - label=3, group=1, id=1, - attributes={'is_crowd': False}), - Polygon([0, 0, 4, 0, 4, 4], - label=3, group=1, id=1, - attributes={'is_crowd': False}), - - Points([1, 2, 3, 4, 2, 3], - group=2, id=2, - attributes={'is_crowd': False}), - Polygon([1, 2, 3, 2, 3, 4, 1, 4], - group=2, id=2, - attributes={'is_crowd': False}), - ]), - DatasetItem(id=2, subset='train', - annotations=[ - Points([1, 2, 0, 2, 4, 1], - label=5, group=3, id=3, - attributes={'is_crowd': False}), - Polygon([0, 1, 4, 1, 4, 2, 0, 2], - label=5, group=3, id=3, - attributes={'is_crowd': False}), - ]), + def test_can_save_dataset_with_no_subsets(self): + test_dataset = Dataset.from_iterable([ + DatasetItem(id=1, attributes={'id': 1}), + DatasetItem(id=2, attributes={'id': 2}), + ]) - DatasetItem(id=3, subset='val', - annotations=[ - Points([0, 0, 1, 2, 3, 4], [0, 1, 2], - group=3, id=3, - attributes={'is_crowd': False}), - Polygon([1, 2, 3, 2, 3, 4, 1, 4], - group=3, id=3, - attributes={'is_crowd': False}), - ]), - ]) + with TestDir() as test_dir: + self._test_save_and_load(test_dataset, + CocoConverter.convert, test_dir) + + def test_can_save_dataset_with_image_info(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=Image(path='1.jpg', size=(10, 15)), + attributes={'id': 1}), + ]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoPersonKeypointsConverter(), test_dir, - target_dataset=DstTestExtractor()) + self._test_save_and_load(expected_dataset, + CocoImageInfoConverter.convert, test_dir) + + def test_relative_paths(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id='1', image=np.ones((4, 2, 3)), + attributes={'id': 1}), + DatasetItem(id='subdir1/1', image=np.ones((2, 6, 3)), + attributes={'id': 2}), + DatasetItem(id='subdir2/1', image=np.ones((5, 4, 3)), + attributes={'id': 3}), + ]) - def test_can_save_dataset_with_no_subsets(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1), - DatasetItem(id=2), - ]) + with TestDir() as test_dir: + self._test_save_and_load(expected_dataset, + partial(CocoImageInfoConverter.convert, save_images=True), test_dir) - def categories(self): - return { AnnotationType.label: LabelCategories() } + def test_preserve_coco_ids(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id='some/name1', image=np.ones((4, 2, 3)), + attributes={'id': 40}), + ]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoConverter(), test_dir) + self._test_save_and_load(expected_dataset, + partial(CocoImageInfoConverter.convert, save_images=True), test_dir) - def test_can_save_dataset_with_image_info(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=Image(path='1.jpg', size=(10, 15))), - ]) + def test_annotation_attributes(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.ones((4, 2, 3)), annotations=[ + Polygon([0, 0, 4, 0, 4, 4], label=5, group=1, id=1, + attributes={'is_crowd': False, 'x': 5, 'y': 'abc'}), + ], attributes={'id': 1}) + ], categories=[str(i) for i in range(10)]) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - CocoConverter(tasks='image_info'), test_dir) \ No newline at end of file + self._test_save_and_load(expected_dataset, + CocoConverter.convert, test_dir) diff --git a/datumaro/tests/test_cvat_format.py b/datumaro/tests/test_cvat_format.py index c2879ef8..5c246ff4 100644 --- a/datumaro/tests/test_cvat_format.py +++ b/datumaro/tests/test_cvat_format.py @@ -1,148 +1,142 @@ +from functools import partial import numpy as np -import os import os.path as osp -from xml.etree import ElementTree as ET from unittest import TestCase - +from datumaro.components.project import Dataset from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Points, Polygon, PolyLine, Bbox, Label, LabelCategories, ) from datumaro.plugins.cvat_format.importer import CvatImporter from datumaro.plugins.cvat_format.converter import CvatConverter -from datumaro.plugins.cvat_format.format import CvatPath -from datumaro.util.image import save_image, Image +from datumaro.util.image import Image from datumaro.util.test_utils import TestDir, compare_datasets -def generate_dummy_cvat(path): - images_dir = osp.join(path, CvatPath.IMAGES_DIR) - anno_dir = osp.join(path, CvatPath.ANNOTATIONS_DIR) - - os.makedirs(images_dir) - os.makedirs(anno_dir) - - root_elem = ET.Element('annotations') - ET.SubElement(root_elem, 'version').text = '1.1' - - meta_elem = ET.SubElement(root_elem, 'meta') - task_elem = ET.SubElement(meta_elem, 'task') - ET.SubElement(task_elem, 'z_order').text = 'True' - ET.SubElement(task_elem, 'mode').text = 'interpolation' - - labels_elem = ET.SubElement(task_elem, 'labels') - - label1_elem = ET.SubElement(labels_elem, 'label') - ET.SubElement(label1_elem, 'name').text = 'label1' - label1_attrs_elem = ET.SubElement(label1_elem, 'attributes') - - label1_a1_elem = ET.SubElement(label1_attrs_elem, 'attribute') - ET.SubElement(label1_a1_elem, 'name').text = 'a1' - ET.SubElement(label1_a1_elem, 'input_type').text = 'checkbox' - ET.SubElement(label1_a1_elem, 'default_value').text = 'false' - ET.SubElement(label1_a1_elem, 'values').text = 'false\ntrue' - - label1_a2_elem = ET.SubElement(label1_attrs_elem, 'attribute') - ET.SubElement(label1_a2_elem, 'name').text = 'a2' - ET.SubElement(label1_a2_elem, 'input_type').text = 'radio' - ET.SubElement(label1_a2_elem, 'default_value').text = 'v1' - ET.SubElement(label1_a2_elem, 'values').text = 'v1\nv2\nv3' +DUMMY_IMAGE_DATASET_DIR = osp.join(osp.dirname(__file__), + 'assets', 'cvat_dataset', 'for_images') - label2_elem = ET.SubElement(labels_elem, 'label') - ET.SubElement(label2_elem, 'name').text = 'label2' - - # item 1 - save_image(osp.join(images_dir, 'img0.jpg'), np.ones((8, 8, 3))) - item1_elem = ET.SubElement(root_elem, 'image') - item1_elem.attrib.update({ - 'id': '0', 'name': 'img0', 'width': '8', 'height': '8' - }) - - item1_ann1_elem = ET.SubElement(item1_elem, 'box') - item1_ann1_elem.attrib.update({ - 'label': 'label1', 'occluded': '1', 'z_order': '1', - 'xtl': '0', 'ytl': '2', 'xbr': '4', 'ybr': '4' - }) - item1_ann1_a1_elem = ET.SubElement(item1_ann1_elem, 'attribute') - item1_ann1_a1_elem.attrib['name'] = 'a1' - item1_ann1_a1_elem.text = 'true' - item1_ann1_a2_elem = ET.SubElement(item1_ann1_elem, 'attribute') - item1_ann1_a2_elem.attrib['name'] = 'a2' - item1_ann1_a2_elem.text = 'v3' - - item1_ann2_elem = ET.SubElement(item1_elem, 'polyline') - item1_ann2_elem.attrib.update({ - 'label': '', 'points': '1.0,2;3,4;5,6;7,8' - }) - - # item 2 - save_image(osp.join(images_dir, 'img1.jpg'), np.ones((10, 10, 3))) - item2_elem = ET.SubElement(root_elem, 'image') - item2_elem.attrib.update({ - 'id': '1', 'name': 'img1', 'width': '10', 'height': '10' - }) - - item2_ann1_elem = ET.SubElement(item2_elem, 'polygon') - item2_ann1_elem.attrib.update({ - 'label': '', 'points': '1,2;3,4;6,5', 'z_order': '1', - }) - - item2_ann2_elem = ET.SubElement(item2_elem, 'points') - item2_ann2_elem.attrib.update({ - 'label': 'label2', 'points': '1,2;3,4;5,6', 'z_order': '2', - }) - - with open(osp.join(anno_dir, 'train.xml'), 'w') as f: - f.write(ET.tostring(root_elem, encoding='unicode')) +DUMMY_VIDEO_DATASET_DIR = osp.join(osp.dirname(__file__), + 'assets', 'cvat_dataset', 'for_video') class CvatImporterTest(TestCase): - def test_can_detect(self): - with TestDir() as test_dir: - generate_dummy_cvat(test_dir) - - self.assertTrue(CvatImporter.detect(test_dir)) - -class CvatExtractorTest(TestCase): - def test_can_load(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=0, subset='train', image=np.ones((8, 8, 3)), - annotations=[ - Bbox(0, 2, 4, 2, label=0, z_order=1, - attributes={ - 'occluded': True, - 'a1': True, 'a2': 'v3' - }), - PolyLine([1, 2, 3, 4, 5, 6, 7, 8], z_order=0, - attributes={'occluded': False}), - ]), - DatasetItem(id=1, subset='train', image=np.ones((10, 10, 3)), - annotations=[ - Polygon([1, 2, 3, 4, 6, 5], z_order=1, - attributes={'occluded': False}), - Points([1, 2, 3, 4, 5, 6], label=1, z_order=2, - attributes={'occluded': False}), - ]), - ]) - - def categories(self): - label_categories = LabelCategories() - label_categories.add('label1', attributes={'a1', 'a2'}) - label_categories.add('label2') - return { - AnnotationType.label: label_categories, - } - - with TestDir() as test_dir: - generate_dummy_cvat(test_dir) - source_dataset = TestExtractor() - - parsed_dataset = CvatImporter()(test_dir).make_dataset() - - compare_datasets(self, source_dataset, parsed_dataset) - + def test_can_detect_image(self): + self.assertTrue(CvatImporter.detect(DUMMY_IMAGE_DATASET_DIR)) + + def test_can_detect_video(self): + self.assertTrue(CvatImporter.detect(DUMMY_VIDEO_DATASET_DIR)) + + def test_can_load_image(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id='img0', subset='train', + image=np.ones((8, 8, 3)), + annotations=[ + Bbox(0, 2, 4, 2, label=0, z_order=1, + attributes={ + 'occluded': True, + 'a1': True, 'a2': 'v3' + }), + PolyLine([1, 2, 3, 4, 5, 6, 7, 8], + attributes={'occluded': False}), + ], attributes={'frame': 0}), + DatasetItem(id='img1', subset='train', + image=np.ones((10, 10, 3)), + annotations=[ + Polygon([1, 2, 3, 4, 6, 5], z_order=1, + attributes={'occluded': False}), + Points([1, 2, 3, 4, 5, 6], label=1, z_order=2, + attributes={'occluded': False}), + ], attributes={'frame': 1}), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable([ + ['label1', '', {'a1', 'a2'}], + ['label2'], + ]) + }) + + parsed_dataset = CvatImporter()(DUMMY_IMAGE_DATASET_DIR).make_dataset() + + compare_datasets(self, expected_dataset, parsed_dataset) + + def test_can_load_video(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id='frame_000010', subset='annotations', + image=np.ones((20, 25, 3)), + annotations=[ + Bbox(3, 4, 7, 1, label=2, + id=0, + attributes={ + 'occluded': True, + 'outside': False, 'keyframe': True, + 'track_id': 0 + }), + Points([21.95, 8.00, 2.55, 15.09, 2.23, 3.16], + label=0, + id=1, + attributes={ + 'occluded': False, + 'outside': False, 'keyframe': True, + 'track_id': 1, 'hgl': 'hgkf', + }), + ], attributes={'frame': 10}), + DatasetItem(id='frame_000013', subset='annotations', + image=np.ones((20, 25, 3)), + annotations=[ + Bbox(7, 6, 7, 2, label=2, + id=0, + attributes={ + 'occluded': False, + 'outside': True, 'keyframe': True, + 'track_id': 0 + }), + Points([21.95, 8.00, 9.55, 15.09, 5.23, 1.16], + label=0, + id=1, + attributes={ + 'occluded': False, + 'outside': True, 'keyframe': True, + 'track_id': 1, 'hgl': 'jk', + }), + PolyLine([7.85, 13.88, 3.50, 6.67, 15.90, 2.00, 13.31, 7.21], + label=2, + id=2, + attributes={ + 'occluded': False, + 'outside': False, 'keyframe': True, + 'track_id': 2, + }), + ], attributes={'frame': 13}), + DatasetItem(id='frame_000016', subset='annotations', + image=Image(path='frame_0000016.png', size=(20, 25)), + annotations=[ + Bbox(8, 7, 6, 10, label=2, + id=0, + attributes={ + 'occluded': False, + 'outside': True, 'keyframe': True, + 'track_id': 0 + }), + PolyLine([7.85, 13.88, 3.50, 6.67, 15.90, 2.00, 13.31, 7.21], + label=2, + id=2, + attributes={ + 'occluded': False, + 'outside': True, 'keyframe': True, + 'track_id': 2, + }), + ], attributes={'frame': 16}), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable([ + ['klhg', '', {'hgl'}], + ['z U k'], + ['II'] + ]), + }) + + parsed_dataset = CvatImporter()(DUMMY_VIDEO_DATASET_DIR).make_dataset() + + compare_datasets(self, expected_dataset, parsed_dataset) class CvatConverterTest(TestCase): def _test_save_and_load(self, source_dataset, converter, test_dir, @@ -165,95 +159,120 @@ class CvatConverterTest(TestCase): label_categories.items[2].attributes.update(['a1', 'a2']) label_categories.attributes.update(['occluded']) - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=0, subset='s1', image=np.zeros((5, 10, 3)), - annotations=[ - Polygon([0, 0, 4, 0, 4, 4], - label=1, group=4, - attributes={ 'occluded': True }), - Polygon([5, 0, 9, 0, 5, 5], - label=2, group=4, - attributes={ 'unknown': 'bar' }), - Points([1, 1, 3, 2, 2, 3], - label=2, - attributes={ 'a1': 'x', 'a2': 42 }), - Label(1), - Label(2, attributes={ 'a1': 'y', 'a2': 44 }), - ] - ), - DatasetItem(id=1, subset='s1', - annotations=[ - PolyLine([0, 0, 4, 0, 4, 4], - label=3, id=4, group=4), - Bbox(5, 0, 1, 9, - label=3, id=4, group=4), - ] - ), + source_dataset = Dataset.from_iterable([ + DatasetItem(id=0, subset='s1', image=np.zeros((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=1, group=4, + attributes={ 'occluded': True }), + Points([1, 1, 3, 2, 2, 3], + label=2, + attributes={ 'a1': 'x', 'a2': 42, + 'unknown': 'bar' }), + Label(1), + Label(2, attributes={ 'a1': 'y', 'a2': 44 }), + ] + ), + DatasetItem(id=1, subset='s1', + annotations=[ + PolyLine([0, 0, 4, 0, 4, 4], + label=3, id=4, group=4), + Bbox(5, 0, 1, 9, + label=3, id=4, group=4), + ] + ), + + DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], z_order=1, + label=3, group=4, + attributes={ 'occluded': False }), + PolyLine([5, 0, 9, 0, 5, 5]), # will be skipped as no label + ] + ), + + DatasetItem(id=3, subset='s3', image=Image( + path='3.jpg', size=(2, 4))), + ], categories={ + AnnotationType.label: label_categories, + }) + + target_dataset = Dataset.from_iterable([ + DatasetItem(id=0, subset='s1', image=np.zeros((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], + label=1, group=4, + attributes={ 'occluded': True }), + Points([1, 1, 3, 2, 2, 3], + label=2, + attributes={ 'occluded': False, + 'a1': 'x', 'a2': 42 }), + Label(1), + Label(2, attributes={ 'a1': 'y', 'a2': 44 }), + ], attributes={'frame': 0} + ), + DatasetItem(id=1, subset='s1', + annotations=[ + PolyLine([0, 0, 4, 0, 4, 4], + label=3, group=4, + attributes={ 'occluded': False }), + Bbox(5, 0, 1, 9, + label=3, group=4, + attributes={ 'occluded': False }), + ], attributes={'frame': 1} + ), + + DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4], z_order=1, + label=3, group=4, + attributes={ 'occluded': False }), + ], attributes={'frame': 0} + ), + + DatasetItem(id=3, subset='s3', image=Image( + path='3.jpg', size=(2, 4)), + attributes={'frame': 0}), + ], categories={ + AnnotationType.label: label_categories, + }) - DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), - annotations=[ - Polygon([0, 0, 4, 0, 4, 4], z_order=1, - label=3, group=4, - attributes={ 'occluded': False }), - PolyLine([5, 0, 9, 0, 5, 5]), # will be skipped as no label - ] - ), - - DatasetItem(id=3, subset='s3', image=Image( - path='3.jpg', size=(2, 4))), - ]) - - def categories(self): - return { AnnotationType.label: label_categories } - - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=0, subset='s1', image=np.zeros((5, 10, 3)), - annotations=[ - Polygon([0, 0, 4, 0, 4, 4], z_order=0, - label=1, group=4, - attributes={ 'occluded': True }), - Polygon([5, 0, 9, 0, 5, 5], z_order=0, - label=2, group=4, - attributes={ 'occluded': False }), - Points([1, 1, 3, 2, 2, 3], z_order=0, - label=2, - attributes={ 'occluded': False, - 'a1': 'x', 'a2': 42 }), - Label(1), - Label(2, attributes={ 'a1': 'y', 'a2': 44 }), - ] - ), - DatasetItem(id=1, subset='s1', - annotations=[ - PolyLine([0, 0, 4, 0, 4, 4], z_order=0, - label=3, group=4, - attributes={ 'occluded': False }), - Bbox(5, 0, 1, 9, z_order=0, - label=3, group=4, - attributes={ 'occluded': False }), - ] - ), - - DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), - annotations=[ - Polygon([0, 0, 4, 0, 4, 4], z_order=1, - label=3, group=4, - attributes={ 'occluded': False }), - ] - ), - - DatasetItem(id=3, subset='s3', image=Image( - path='3.jpg', size=(2, 4))), - ]) + with TestDir() as test_dir: + self._test_save_and_load(source_dataset, + partial(CvatConverter.convert, save_images=True), test_dir, + target_dataset=target_dataset) + + def test_relative_paths(self): + source_dataset = Dataset.from_iterable([ + DatasetItem(id='1', image=np.ones((4, 2, 3))), + DatasetItem(id='subdir1/1', image=np.ones((2, 6, 3))), + DatasetItem(id='subdir2/1', image=np.ones((5, 4, 3))), + ], categories={ AnnotationType.label: LabelCategories() }) + + target_dataset = Dataset.from_iterable([ + DatasetItem(id='1', image=np.ones((4, 2, 3)), + attributes={'frame': 0}), + DatasetItem(id='subdir1/1', image=np.ones((2, 6, 3)), + attributes={'frame': 1}), + DatasetItem(id='subdir2/1', image=np.ones((5, 4, 3)), + attributes={'frame': 2}), + ], categories={ + AnnotationType.label: LabelCategories() + }) - def categories(self): - return { AnnotationType.label: label_categories } + with TestDir() as test_dir: + self._test_save_and_load(source_dataset, + partial(CvatConverter.convert, save_images=True), test_dir, + target_dataset=target_dataset) + + def test_preserve_frame_ids(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id='some/name1', image=np.ones((4, 2, 3)), + attributes={'frame': 40}), + ], categories={ + AnnotationType.label: LabelCategories() + }) with TestDir() as test_dir: - self._test_save_and_load(SrcExtractor(), - CvatConverter(save_images=True), test_dir, - target_dataset=DstExtractor()) + self._test_save_and_load(expected_dataset, + CvatConverter.convert, test_dir) diff --git a/datumaro/tests/test_datumaro_format.py b/datumaro/tests/test_datumaro_format.py index d6a81a53..8faf5ef0 100644 --- a/datumaro/tests/test_datumaro_format.py +++ b/datumaro/tests/test_datumaro_format.py @@ -1,7 +1,8 @@ +from functools import partial import numpy as np from unittest import TestCase - +from datumaro.components.project import Dataset from datumaro.components.project import Project from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Label, Mask, Points, Polygon, @@ -12,97 +13,96 @@ from datumaro.plugins.datumaro_format.importer import DatumaroImporter from datumaro.plugins.datumaro_format.converter import DatumaroConverter from datumaro.util.mask_tools import generate_colormap from datumaro.util.image import Image -from datumaro.util.test_utils import TestDir, item_to_str +from datumaro.util.test_utils import TestDir, compare_datasets_strict class DatumaroConverterTest(TestCase): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=100, subset='train', image=np.ones((10, 6, 3)), - annotations=[ - Caption('hello', id=1), - Caption('world', id=2, group=5), - Label(2, id=3, attributes={ - 'x': 1, - 'y': '2', - }), - Bbox(1, 2, 3, 4, label=4, id=4, z_order=1, attributes={ - 'score': 1.0, - }), - Bbox(5, 6, 7, 8, id=5, group=5), - Points([1, 2, 2, 0, 1, 1], label=0, id=5, z_order=4), - Mask(label=3, id=5, z_order=2, image=np.ones((2, 3))), - ]), - DatasetItem(id=21, subset='train', - annotations=[ - Caption('test'), - Label(2), - Bbox(1, 2, 3, 4, 5, id=42, group=42) - ]), - - DatasetItem(id=2, subset='val', - annotations=[ - PolyLine([1, 2, 3, 4, 5, 6, 7, 8], id=11, z_order=1), - Polygon([1, 2, 3, 4, 5, 6, 7, 8], id=12, z_order=4), - ]), - - DatasetItem(id=42, subset='test'), - - DatasetItem(id=42), - DatasetItem(id=43, image=Image(path='1/b/c.qq', size=(2, 4))), - ]) - - def categories(self): - label_categories = LabelCategories() - for i in range(5): - label_categories.add('cat' + str(i)) - - mask_categories = MaskCategories( - generate_colormap(len(label_categories.items))) - - points_categories = PointsCategories() - for index, _ in enumerate(label_categories.items): - points_categories.add(index, ['cat1', 'cat2'], joints=[[0, 1]]) - - return { - AnnotationType.label: label_categories, - AnnotationType.mask: mask_categories, - AnnotationType.points: points_categories, - } + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None): + converter(source_dataset, test_dir) + + if importer_args is None: + importer_args = {} + parsed_dataset = Project.import_from( + test_dir, 'datumaro', **importer_args).make_dataset() + + if target_dataset is None: + target_dataset = source_dataset + + compare_datasets_strict(self, + expected=target_dataset, actual=parsed_dataset) + + @property + def test_dataset(self): + label_categories = LabelCategories() + for i in range(5): + label_categories.add('cat' + str(i)) + + mask_categories = MaskCategories( + generate_colormap(len(label_categories.items))) + + points_categories = PointsCategories() + for index, _ in enumerate(label_categories.items): + points_categories.add(index, ['cat1', 'cat2'], joints=[[0, 1]]) + + return Dataset.from_iterable([ + DatasetItem(id=100, subset='train', image=np.ones((10, 6, 3)), + annotations=[ + Caption('hello', id=1), + Caption('world', id=2, group=5), + Label(2, id=3, attributes={ + 'x': 1, + 'y': '2', + }), + Bbox(1, 2, 3, 4, label=4, id=4, z_order=1, attributes={ + 'score': 1.0, + }), + Bbox(5, 6, 7, 8, id=5, group=5), + Points([1, 2, 2, 0, 1, 1], label=0, id=5, z_order=4), + Mask(label=3, id=5, z_order=2, image=np.ones((2, 3))), + ]), + DatasetItem(id=21, subset='train', + annotations=[ + Caption('test'), + Label(2), + Bbox(1, 2, 3, 4, label=5, id=42, group=42) + ]), + + DatasetItem(id=2, subset='val', + annotations=[ + PolyLine([1, 2, 3, 4, 5, 6, 7, 8], id=11, z_order=1), + Polygon([1, 2, 3, 4, 5, 6, 7, 8], id=12, z_order=4), + ]), + + DatasetItem(id=42, subset='test', + attributes={'a1': 5, 'a2': '42'}), + + DatasetItem(id=42), + DatasetItem(id=43, image=Image(path='1/b/c.qq', size=(2, 4))), + ], categories={ + AnnotationType.label: label_categories, + AnnotationType.mask: mask_categories, + AnnotationType.points: points_categories, + }) def test_can_save_and_load(self): with TestDir() as test_dir: - source_dataset = self.TestExtractor() - - converter = DatumaroConverter(save_images=True) - converter(source_dataset, test_dir) - - project = Project.import_from(test_dir, 'datumaro') - parsed_dataset = project.make_dataset() - - self.assertListEqual( - sorted(source_dataset.subsets()), - sorted(parsed_dataset.subsets()), - ) + self._test_save_and_load(self.test_dataset, + partial(DatumaroConverter.convert, save_images=True), test_dir) - self.assertEqual(len(source_dataset), len(parsed_dataset)) + def test_can_detect(self): + with TestDir() as test_dir: + DatumaroConverter.convert(self.test_dataset, save_dir=test_dir) - for subset_name in source_dataset.subsets(): - source_subset = source_dataset.get_subset(subset_name) - parsed_subset = parsed_dataset.get_subset(subset_name) - self.assertEqual(len(source_subset), len(parsed_subset)) - for idx, (item_a, item_b) in enumerate( - zip(source_subset, parsed_subset)): - self.assertEqual(item_a, item_b, '%s:\n%s\nvs.\n%s\n' % \ - (idx, item_to_str(item_a), item_to_str(item_b))) + self.assertTrue(DatumaroImporter.detect(test_dir)) - self.assertEqual( - source_dataset.categories(), - parsed_dataset.categories()) + def test_relative_paths(self): + test_dataset = Dataset.from_iterable([ + DatasetItem(id='1', image=np.ones((4, 2, 3))), + DatasetItem(id='subdir1/1', image=np.ones((2, 6, 3))), + DatasetItem(id='subdir2/1', image=np.ones((5, 4, 3))), + ]) - def test_can_detect(self): with TestDir() as test_dir: - DatumaroConverter()(self.TestExtractor(), save_dir=test_dir) - - self.assertTrue(DatumaroImporter.detect(test_dir)) \ No newline at end of file + self._test_save_and_load(test_dataset, + partial(DatumaroConverter.convert, save_images=True), test_dir) diff --git a/datumaro/tests/test_image.py b/datumaro/tests/test_image.py index bdb29438..5f4ef81c 100644 --- a/datumaro/tests/test_image.py +++ b/datumaro/tests/test_image.py @@ -51,3 +51,14 @@ class ImageOperationsTest(TestCase): self.assertTrue(np.array_equal(src_image, dst_image), 'save: %s, load: %s' % (save_backend, load_backend)) + + def test_save_image_to_inexistent_dir_raises_error(self): + with self.assertRaises(FileNotFoundError): + image_module.save_image('some/path.jpg', np.ones((5, 4, 3)), + create_dir=False) + + def test_save_image_can_create_dir(self): + with TestDir() as test_dir: + path = osp.join(test_dir, 'some', 'path.jpg') + image_module.save_image(path, np.ones((5, 4, 3)), create_dir=True) + self.assertTrue(osp.isfile(path)) diff --git a/datumaro/tests/test_image_dir_format.py b/datumaro/tests/test_image_dir_format.py index 30dd05b1..b991220e 100644 --- a/datumaro/tests/test_image_dir_format.py +++ b/datumaro/tests/test_image_dir_format.py @@ -9,20 +9,40 @@ from datumaro.util.test_utils import TestDir, compare_datasets class ImageDirFormatTest(TestCase): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.ones((10, 6, 3))), - DatasetItem(id=2, image=np.ones((5, 4, 3))), - ]) - def test_can_load(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.ones((10, 6, 3))), + DatasetItem(id=2, image=np.ones((5, 4, 3))), + ]) + + with TestDir() as test_dir: + source_dataset = TestExtractor() + + ImageDirConverter.convert(source_dataset, save_dir=test_dir) + + project = Project.import_from(test_dir, 'image_dir') + parsed_dataset = project.make_dataset() + + compare_datasets(self, source_dataset, parsed_dataset) + + def test_relative_paths(self): + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id='1', image=np.ones((4, 2, 3))), + DatasetItem(id='subdir1/1', image=np.ones((2, 6, 3))), + DatasetItem(id='subdir2/1', image=np.ones((5, 4, 3))), + ]) + with TestDir() as test_dir: - source_dataset = self.TestExtractor() + source_dataset = TestExtractor() - ImageDirConverter()(source_dataset, save_dir=test_dir) + ImageDirConverter.convert(source_dataset, save_dir=test_dir) project = Project.import_from(test_dir, 'image_dir') parsed_dataset = project.make_dataset() compare_datasets(self, source_dataset, parsed_dataset) + diff --git a/datumaro/tests/test_labelme_format.py b/datumaro/tests/test_labelme_format.py index 098fd263..d40938bd 100644 --- a/datumaro/tests/test_labelme_format.py +++ b/datumaro/tests/test_labelme_format.py @@ -1,13 +1,14 @@ +from functools import partial import numpy as np import os.path as osp from unittest import TestCase - +from datumaro.components.project import Dataset from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Bbox, Mask, Polygon, LabelCategories ) -from datumaro.components.project import Dataset -from datumaro.plugins.labelme_format import LabelMeExtractor, LabelMeImporter, \ +from datumaro.components.project import Project +from datumaro.plugins.labelme_format import LabelMeImporter, \ LabelMeConverter from datumaro.util.test_utils import TestDir, compare_datasets @@ -28,193 +29,178 @@ class LabelMeConverterTest(TestCase): compare_datasets(self, expected=target_dataset, actual=parsed_dataset) def test_can_save_and_load(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(0, 4, 4, 8, label=2, group=2), - Polygon([0, 4, 4, 4, 5, 6], label=3, attributes={ - 'occluded': True, - 'a1': 'qwe', - 'a2': True, - 'a3': 123, - }), - Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2, - attributes={ 'username': 'test' }), - Bbox(1, 2, 3, 4, group=3), - Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=3, - attributes={ 'occluded': True } - ), - ] + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2, group=2), + Polygon([0, 4, 4, 4, 5, 6], label=3, attributes={ + 'occluded': True, + 'a1': 'qwe', + 'a2': True, + 'a3': 123, + }), + Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2, + attributes={ 'username': 'test' }), + Bbox(1, 2, 3, 4, group=3), + Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=3, + attributes={ 'occluded': True } ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } - - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(0, 4, 4, 8, label=0, group=2, id=0, - attributes={ - 'occluded': False, 'username': '', - } - ), - Polygon([0, 4, 4, 4, 5, 6], label=1, id=1, - attributes={ - 'occluded': True, 'username': '', - 'a1': 'qwe', - 'a2': True, - 'a3': 123, - } - ), - Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2, - id=2, attributes={ - 'occluded': False, 'username': 'test' - } - ), - Bbox(1, 2, 3, 4, group=1, id=3, attributes={ - 'occluded': False, 'username': '', - }), - Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=1, - id=4, attributes={ - 'occluded': True, 'username': '' - } - ), - ] + ] + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) + + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=0, group=2, id=0, + attributes={ + 'occluded': False, 'username': '', + } ), - ]) - - def categories(self): - label_cat = LabelCategories() - label_cat.add('label_2') - label_cat.add('label_3') - return { - AnnotationType.label: label_cat, - } + Polygon([0, 4, 4, 4, 5, 6], label=1, id=1, + attributes={ + 'occluded': True, 'username': '', + 'a1': 'qwe', + 'a2': True, + 'a3': 123, + } + ), + Mask(np.array([[0, 1], [1, 0], [1, 1]]), group=2, + id=2, attributes={ + 'occluded': False, 'username': 'test' + } + ), + Bbox(1, 2, 3, 4, group=1, id=3, attributes={ + 'occluded': False, 'username': '', + }), + Mask(np.array([[0, 0], [0, 0], [1, 1]]), group=1, + id=4, attributes={ + 'occluded': True, 'username': '' + } + ), + ] + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable([ + 'label_2', 'label_3']), + }) with TestDir() as test_dir: self._test_save_and_load( - SrcExtractor(), LabelMeConverter(save_images=True), - test_dir, target_dataset=DstExtractor()) + source_dataset, + partial(LabelMeConverter.convert, save_images=True), + test_dir, target_dataset=target_dataset) + def test_cant_save_dataset_with_relative_paths(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id='dir/1', image=np.ones((2, 6, 3))), + ], categories={ + AnnotationType.label: LabelCategories(), + }) -DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'labelme_dataset') + with self.assertRaisesRegex(Exception, r'only supports flat'): + with TestDir() as test_dir: + self._test_save_and_load(expected_dataset, + LabelMeConverter.convert, test_dir) -class LabelMeExtractorTest(TestCase): - def test_can_load(self): - class DstExtractor(Extractor): - def __iter__(self): - img1 = np.ones((77, 102, 3)) * 255 - img1[6:32, 7:41] = 0 - - mask1 = np.zeros((77, 102), dtype=int) - mask1[67:69, 58:63] = 1 - - mask2 = np.zeros((77, 102), dtype=int) - mask2[13:25, 54:71] = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - return iter([ - DatasetItem(id='img1', image=img1, - annotations=[ - Polygon([43, 34, 45, 34, 45, 37, 43, 37], - label=0, id=0, - attributes={ - 'occluded': False, - 'username': 'admin' - } - ), - Mask(mask1, label=1, id=1, - attributes={ - 'occluded': False, - 'username': 'brussell' - } - ), - Polygon([30, 12, 42, 21, 24, 26, 15, 22, 18, 14, 22, 12, 27, 12], - label=2, group=2, id=2, - attributes={ - 'a1': True, - 'occluded': True, - 'username': 'anonymous' - } - ), - Polygon([35, 21, 43, 22, 40, 28, 28, 31, 31, 22, 32, 25], - label=3, group=2, id=3, - attributes={ - 'kj': True, - 'occluded': False, - 'username': 'anonymous' - } - ), - Bbox(13, 19, 10, 11, label=4, group=2, id=4, - attributes={ - 'hg': True, - 'occluded': True, - 'username': 'anonymous' - } - ), - Mask(mask2, label=5, group=1, id=5, - attributes={ - 'd': True, - 'occluded': False, - 'username': 'anonymous' - } - ), - Polygon([64, 21, 74, 24, 72, 32, 62, 34, 60, 27, 62, 22], - label=6, group=1, id=6, - attributes={ - 'gfd lkj lkj hi': True, - 'occluded': False, - 'username': 'anonymous' - } - ), - ] - ), - ]) - - def categories(self): - label_cat = LabelCategories() - label_cat.add('window') - label_cat.add('license plate') - label_cat.add('o1') - label_cat.add('q1') - label_cat.add('b1') - label_cat.add('m1') - label_cat.add('hg') - return { - AnnotationType.label: label_cat, - } - - parsed = Dataset.from_extractors(LabelMeExtractor(DUMMY_DATASET_DIR)) - compare_datasets(self, expected=DstExtractor(), actual=parsed) +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'labelme_dataset') class LabelMeImporterTest(TestCase): def test_can_detect(self): self.assertTrue(LabelMeImporter.detect(DUMMY_DATASET_DIR)) def test_can_import(self): - parsed = LabelMeImporter()(DUMMY_DATASET_DIR).make_dataset() - self.assertEqual(1, len(parsed)) + img1 = np.ones((77, 102, 3)) * 255 + img1[6:32, 7:41] = 0 + + mask1 = np.zeros((77, 102), dtype=int) + mask1[67:69, 58:63] = 1 + + mask2 = np.zeros((77, 102), dtype=int) + mask2[13:25, 54:71] = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ] + + target_dataset = Dataset.from_iterable([ + DatasetItem(id='img1', image=img1, + annotations=[ + Polygon([43, 34, 45, 34, 45, 37, 43, 37], + label=0, id=0, + attributes={ + 'occluded': False, + 'username': 'admin' + } + ), + Mask(mask1, label=1, id=1, + attributes={ + 'occluded': False, + 'username': 'brussell' + } + ), + Polygon([30, 12, 42, 21, 24, 26, 15, 22, 18, 14, 22, 12, 27, 12], + label=2, group=2, id=2, + attributes={ + 'a1': True, + 'occluded': True, + 'username': 'anonymous' + } + ), + Polygon([35, 21, 43, 22, 40, 28, 28, 31, 31, 22, 32, 25], + label=3, group=2, id=3, + attributes={ + 'kj': True, + 'occluded': False, + 'username': 'anonymous' + } + ), + Bbox(13, 19, 10, 11, label=4, group=2, id=4, + attributes={ + 'hg': True, + 'occluded': True, + 'username': 'anonymous' + } + ), + Mask(mask2, label=5, group=1, id=5, + attributes={ + 'd': True, + 'occluded': False, + 'username': 'anonymous' + } + ), + Polygon([64, 21, 74, 24, 72, 32, 62, 34, 60, 27, 62, 22], + label=6, group=1, id=6, + attributes={ + 'gfd lkj lkj hi': True, + 'occluded': False, + 'username': 'anonymous' + } + ), + ] + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable([ + 'window', 'license plate', 'o1', + 'q1', 'b1', 'm1', 'hg', + ]), + }) + + parsed = Project.import_from(DUMMY_DATASET_DIR, 'label_me') \ + .make_dataset() + compare_datasets(self, expected=target_dataset, actual=parsed) \ No newline at end of file diff --git a/datumaro/tests/test_mot_format.py b/datumaro/tests/test_mot_format.py index efe62502..4cc2a98b 100644 --- a/datumaro/tests/test_mot_format.py +++ b/datumaro/tests/test_mot_format.py @@ -1,10 +1,13 @@ +from functools import partial import numpy as np +import os.path as osp from unittest import TestCase - +from datumaro.components.project import Dataset from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Bbox, LabelCategories ) +from datumaro.components.project import Project from datumaro.plugins.mot_format import MotSeqGtConverter, MotSeqImporter from datumaro.util.test_utils import TestDir, compare_datasets @@ -25,122 +28,109 @@ class MotConverterTest(TestCase): compare_datasets(self, expected=target_dataset, actual=parsed_dataset) def test_can_save_bboxes(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(0, 4, 4, 8, label=2, attributes={ - 'occluded': True, - }), - Bbox(0, 4, 4, 4, label=3, attributes={ - 'visibility': 0.4, - }), - Bbox(2, 4, 4, 4, attributes={ - 'ignored': True - }), - ] - ), - - DatasetItem(id=2, subset='val', - image=np.ones((8, 8, 3)), - annotations=[ - Bbox(1, 2, 4, 2, label=3), - ] - ), - - DatasetItem(id=3, subset='test', - image=np.ones((5, 4, 3)) * 3, - ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } - - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(0, 4, 4, 8, label=2, attributes={ - 'occluded': True, - 'visibility': 0.0, - 'ignored': False, - }), - Bbox(0, 4, 4, 4, label=3, attributes={ - 'occluded': False, - 'visibility': 0.4, - 'ignored': False, - }), - Bbox(2, 4, 4, 4, attributes={ - 'occluded': False, - 'visibility': 1.0, - 'ignored': True, - }), - ] - ), - - DatasetItem(id=2, - image=np.ones((8, 8, 3)), - annotations=[ - Bbox(1, 2, 4, 2, label=3, attributes={ - 'occluded': False, - 'visibility': 1.0, - 'ignored': False, - }), - ] - ), - - DatasetItem(id=3, - image=np.ones((5, 4, 3)) * 3, - ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2, attributes={ + 'occluded': True, + }), + Bbox(0, 4, 4, 4, label=3, attributes={ + 'visibility': 0.4, + }), + Bbox(2, 4, 4, 4, attributes={ + 'ignored': True + }), + ] + ), + + DatasetItem(id=2, subset='val', + image=np.ones((8, 8, 3)), + annotations=[ + Bbox(1, 2, 4, 2, label=3), + ] + ), + + DatasetItem(id=3, subset='test', + image=np.ones((5, 4, 3)) * 3, + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) + + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2, attributes={ + 'occluded': True, + 'visibility': 0.0, + 'ignored': False, + }), + Bbox(0, 4, 4, 4, label=3, attributes={ + 'occluded': False, + 'visibility': 0.4, + 'ignored': False, + }), + Bbox(2, 4, 4, 4, attributes={ + 'occluded': False, + 'visibility': 1.0, + 'ignored': True, + }), + ] + ), + + DatasetItem(id=2, + image=np.ones((8, 8, 3)), + annotations=[ + Bbox(1, 2, 4, 2, label=3, attributes={ + 'occluded': False, + 'visibility': 1.0, + 'ignored': False, + }), + ] + ), + + DatasetItem(id=3, + image=np.ones((5, 4, 3)) * 3, + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) with TestDir() as test_dir: self._test_save_and_load( - SrcExtractor(), MotSeqGtConverter(save_images=True), - test_dir, target_dataset=DstExtractor()) + source_dataset, + partial(MotSeqGtConverter.convert, save_images=True), + test_dir, target_dataset=target_dataset) + + +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'mot_dataset') class MotImporterTest(TestCase): def test_can_detect(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(0, 4, 4, 8, label=2), - ] - ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } - - def generate_dummy_dataset(path): - MotSeqGtConverter()(TestExtractor(), save_dir=path) - - with TestDir() as test_dir: - generate_dummy_dataset(test_dir) + self.assertTrue(MotSeqImporter.detect(DUMMY_DATASET_DIR)) + + def test_can_import(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id=1, + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2, attributes={ + 'occluded': False, + 'visibility': 1.0, + 'ignored': False, + }), + ] + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) + + dataset = Project.import_from(DUMMY_DATASET_DIR, 'mot_seq') \ + .make_dataset() - self.assertTrue(MotSeqImporter.detect(test_dir)) + compare_datasets(self, expected_dataset, dataset) \ No newline at end of file diff --git a/datumaro/tests/test_ops.py b/datumaro/tests/test_ops.py new file mode 100644 index 00000000..dd4520b5 --- /dev/null +++ b/datumaro/tests/test_ops.py @@ -0,0 +1,367 @@ +from unittest import TestCase + +import numpy as np + +from datumaro.components.extractor import (Bbox, Caption, DatasetItem, + Extractor, Label, Mask, Points, Polygon, PolyLine) +from datumaro.components.operations import (FailedAttrVotingError, + IntersectMerge, NoMatchingAnnError, NoMatchingItemError, WrongGroupError, + compute_ann_statistics, mean_std) +from datumaro.components.project import Dataset +from datumaro.util.test_utils import compare_datasets + + +class TestOperations(TestCase): + def test_mean_std(self): + expected_mean = [100, 50, 150] + expected_std = [20, 50, 10] + + class TestExtractor(Extractor): + def __iter__(self): + return iter([ + DatasetItem(id=1, image=np.random.normal( + expected_mean, expected_std, + size=(w, h, 3)) + ) + for i, (w, h) in enumerate([ + (3000, 100), (800, 600), (400, 200), (700, 300) + ]) + ]) + + actual_mean, actual_std = mean_std(TestExtractor()) + + for em, am in zip(expected_mean, actual_mean): + self.assertAlmostEqual(em, am, places=0) + for estd, astd in zip(expected_std, actual_std): + self.assertAlmostEqual(estd, astd, places=0) + + def test_stats(self): + dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.ones((5, 5, 3)), annotations=[ + Caption('hello'), + Caption('world'), + Label(2, attributes={ 'x': 1, 'y': '2', }), + Bbox(1, 2, 2, 2, label=2, attributes={ 'score': 0.5, }), + Bbox(5, 6, 2, 2, attributes={ + 'x': 1, 'y': '3', 'occluded': True, + }), + Points([1, 2, 2, 0, 1, 1], label=0), + Mask(label=3, image=np.array([ + [0, 0, 1, 1, 1], + [0, 0, 1, 1, 1], + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ])), + ]), + DatasetItem(id=2, image=np.ones((2, 4, 3)), annotations=[ + Label(2, attributes={ 'x': 2, 'y': '2', }), + Bbox(1, 2, 2, 2, label=3, attributes={ 'score': 0.5, }), + Bbox(5, 6, 2, 2, attributes={ + 'x': 2, 'y': '3', 'occluded': False, + }), + ]), + DatasetItem(id=3), + ], categories=['label_%s' % i for i in range(4)]) + + expected = { + 'images count': 3, + 'annotations count': 10, + 'unannotated images count': 1, + 'unannotated images': ['3'], + 'annotations by type': { + 'label': { 'count': 2, }, + 'polygon': { 'count': 0, }, + 'polyline': { 'count': 0, }, + 'bbox': { 'count': 4, }, + 'mask': { 'count': 1, }, + 'points': { 'count': 1, }, + 'caption': { 'count': 2, }, + }, + 'annotations': { + 'labels': { + 'count': 6, + 'distribution': { + 'label_0': [1, 1/6], + 'label_1': [0, 0.0], + 'label_2': [3, 3/6], + 'label_3': [2, 2/6], + }, + 'attributes': { + 'x': { + 'count': 2, # unnotations with no label are skipped + 'values count': 2, + 'values present': ['1', '2'], + 'distribution': { + '1': [1, 1/2], + '2': [1, 1/2], + }, + }, + 'y': { + 'count': 2, # unnotations with no label are skipped + 'values count': 1, + 'values present': ['2'], + 'distribution': { + '2': [2, 2/2], + }, + }, + # must not include "special" attributes like "occluded" + } + }, + 'segments': { + 'avg. area': (4 * 2 + 9 * 1) / 3, + 'area distribution': [ + {'min': 4.0, 'max': 4.5, 'count': 2, 'percent': 2/3}, + {'min': 4.5, 'max': 5.0, 'count': 0, 'percent': 0.0}, + {'min': 5.0, 'max': 5.5, 'count': 0, 'percent': 0.0}, + {'min': 5.5, 'max': 6.0, 'count': 0, 'percent': 0.0}, + {'min': 6.0, 'max': 6.5, 'count': 0, 'percent': 0.0}, + {'min': 6.5, 'max': 7.0, 'count': 0, 'percent': 0.0}, + {'min': 7.0, 'max': 7.5, 'count': 0, 'percent': 0.0}, + {'min': 7.5, 'max': 8.0, 'count': 0, 'percent': 0.0}, + {'min': 8.0, 'max': 8.5, 'count': 0, 'percent': 0.0}, + {'min': 8.5, 'max': 9.0, 'count': 1, 'percent': 1/3}, + ], + 'pixel distribution': { + 'label_0': [0, 0.0], + 'label_1': [0, 0.0], + 'label_2': [4, 4/17], + 'label_3': [13, 13/17], + }, + } + }, + } + + actual = compute_ann_statistics(dataset) + + self.assertEqual(expected, actual) + +class TestMultimerge(TestCase): + def test_can_match_items(self): + # items 1 and 3 are unique, item 2 is common and should be merged + + source0 = Dataset.from_iterable([ + DatasetItem(1, annotations=[ Label(0), ]), + DatasetItem(2, annotations=[ Label(0), ]), + ], categories=['a', 'b']) + + source1 = Dataset.from_iterable([ + DatasetItem(2, annotations=[ Label(1), ]), + DatasetItem(3, annotations=[ Label(0), ]), + ], categories=['a', 'b']) + + source2 = Dataset.from_iterable([ + DatasetItem(2, annotations=[ Label(0), Bbox(1, 2, 3, 4) ]), + ], categories=['a', 'b']) + + expected = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + Label(0, attributes={'score': 1/3}), + ]), + DatasetItem(2, annotations=[ + Label(0, attributes={'score': 2/3}), + Label(1, attributes={'score': 1/3}), + Bbox(1, 2, 3, 4, attributes={'score': 1.0}), + ]), + DatasetItem(3, annotations=[ + Label(0, attributes={'score': 1/3}), + ]), + ], categories=['a', 'b']) + + merger = IntersectMerge() + merged = merger([source0, source1, source2]) + + compare_datasets(self, expected, merged) + self.assertEqual( + [ + NoMatchingItemError(item_id=('1', ''), sources={1, 2}), + NoMatchingItemError(item_id=('3', ''), sources={0, 2}), + ], + sorted((e for e in merger.errors + if isinstance(e, NoMatchingItemError)), + key=lambda e: e.item_id) + ) + self.assertEqual( + [ + NoMatchingAnnError(item_id=('2', ''), sources={0, 1}, + ann=source2.get('2').annotations[1]), + ], + sorted((e for e in merger.errors + if isinstance(e, NoMatchingAnnError)), + key=lambda e: e.item_id) + ) + + def test_can_match_shapes(self): + source0 = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + # unique + Bbox(1, 2, 3, 4, label=1), + + # common + Mask(label=3, z_order=2, image=np.array([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 0], + [1, 1, 1, 0], + ])), + Polygon([1, 0, 3, 2, 1, 2]), + + # an instance with keypoints + Bbox(4, 5, 2, 4, label=2, z_order=1, group=1), + Points([5, 6], label=0, group=1), + Points([6, 8], label=1, group=1), + + PolyLine([1, 1, 2, 1, 3, 1]), + ]), + ], categories=['a', 'b', 'c']) + + source1 = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + # common + Mask(label=3, image=np.array([ + [0, 0, 0, 0], + [0, 1, 1, 1], + [0, 1, 1, 1], + [0, 1, 1, 1], + ])), + Polygon([0, 2, 2, 0, 2, 1]), + + # an instance with keypoints + Bbox(4, 4, 2, 5, label=2, z_order=1, group=2), + Points([5.5, 6.5], label=0, group=2), + Points([6, 8], label=1, group=2), + + PolyLine([1, 1.5, 2, 1.5]), + ]), + ], categories=['a', 'b', 'c']) + + source2 = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + # common + Mask(label=3, z_order=3, image=np.array([ + [0, 0, 1, 1], + [0, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 0], + ])), + Polygon([3, 1, 2, 2, 0, 1]), + + # an instance with keypoints, one is missing + Bbox(3, 6, 2, 3, label=2, z_order=4, group=3), + Points([4.5, 5.5], label=0, group=3), + + PolyLine([1, 1.25, 3, 1, 4, 2]), + ]), + ], categories=['a', 'b', 'c']) + + expected = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + # unique + Bbox(1, 2, 3, 4, label=1), + + # common + # nearest to mean bbox + Mask(label=3, z_order=3, image=np.array([ + [0, 0, 0, 0], + [0, 1, 1, 1], + [0, 1, 1, 1], + [0, 1, 1, 1], + ])), + Polygon([1, 0, 3, 2, 1, 2]), + + # an instance with keypoints + Bbox(4, 5, 2, 4, label=2, z_order=4, group=1), + Points([5, 6], label=0, group=1), + Points([6, 8], label=1, group=1), + + PolyLine([1, 1.25, 3, 1, 4, 2]), + ]), + ], categories=['a', 'b', 'c']) + + merger = IntersectMerge(conf={'quorum': 1, 'pairwise_dist': 0.1}) + merged = merger([source0, source1, source2]) + + compare_datasets(self, expected, merged, ignored_attrs={'score'}) + self.assertEqual( + [ + NoMatchingAnnError(item_id=('1', ''), sources={2}, + ann=source0.get('1').annotations[5]), + NoMatchingAnnError(item_id=('1', ''), sources={1, 2}, + ann=source0.get('1').annotations[0]), + ], + sorted((e for e in merger.errors + if isinstance(e, NoMatchingAnnError)), + key=lambda e: len(e.sources)) + ) + + def test_attributes(self): + source0 = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + Label(2, attributes={ + 'unique': 1, + 'common_under_quorum': 2, + 'common_over_quorum': 3, + 'ignored': 'q', + }), + ]), + ], categories=['a', 'b', 'c']) + + source1 = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + Label(2, attributes={ + 'common_under_quorum': 2, + 'common_over_quorum': 3, + 'ignored': 'q', + }), + ]), + ], categories=['a', 'b', 'c']) + + source2 = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + Label(2, attributes={ + 'common_over_quorum': 3, + 'ignored': 'q', + }), + ]), + ], categories=['a', 'b', 'c']) + + expected = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + Label(2, attributes={ 'common_over_quorum': 3 }), + ]), + ], categories=['a', 'b', 'c']) + + merger = IntersectMerge(conf={ + 'quorum': 3, 'ignored_attributes': {'ignored'}}) + merged = merger([source0, source1, source2]) + + compare_datasets(self, expected, merged, ignored_attrs={'score'}) + self.assertEqual(2, len([e for e in merger.errors + if isinstance(e, FailedAttrVotingError)]) + ) + + def test_group_checks(self): + dataset = Dataset.from_iterable([ + DatasetItem(1, annotations=[ + Bbox(0, 0, 0, 0, label=0, group=1), # misses an optional label + Bbox(0, 0, 0, 0, label=1, group=1), + + Bbox(0, 0, 0, 0, label=2, group=2), # misses a mandatory label - error + Bbox(0, 0, 0, 0, label=2, group=2), + + Bbox(0, 0, 0, 0, label=4), # misses an optional label + Bbox(0, 0, 0, 0, label=5), # misses a mandatory label - error + Bbox(0, 0, 0, 0, label=0), # misses a mandatory label - error + + Bbox(0, 0, 0, 0, label=3), # not listed - not checked + ]), + ], categories=['a', 'a_g1', 'a_g2_opt', 'b', 'c', 'c_g1_opt']) + + merger = IntersectMerge(conf={'groups': [ + ['a', 'a_g1', 'a_g2_opt?'], ['c', 'c_g1_opt?'] + ]}) + merger([dataset, dataset]) + + self.assertEqual(3, len([e for e in merger.errors + if isinstance(e, WrongGroupError)]), merger.errors + ) diff --git a/datumaro/tests/test_project.py b/datumaro/tests/test_project.py index 75baf716..ed4ad976 100644 --- a/datumaro/tests/test_project.py +++ b/datumaro/tests/test_project.py @@ -6,10 +6,10 @@ from unittest import TestCase from datumaro.components.project import Project, Environment, Dataset from datumaro.components.config_model import Source, Model -from datumaro.components.launcher import Launcher, InferenceWrapper -from datumaro.components.converter import Converter +from datumaro.components.launcher import Launcher, ModelTransform from datumaro.components.extractor import (Extractor, DatasetItem, Label, Mask, Points, Polygon, PolyLine, Bbox, Caption, + LabelCategories, AnnotationType ) from datumaro.util.image import Image from datumaro.components.config import Config, DefaultConfig, SchemaBuilder @@ -133,15 +133,15 @@ class ProjectTest(TestCase): self.assertTrue('project1' in dataset.sources) def test_can_batch_launch_custom_model(self): - class TestExtractor(Extractor): - def __iter__(self): - for i in range(5): - yield DatasetItem(id=i, subset='train', image=np.array([i])) + dataset = Dataset.from_iterable([ + DatasetItem(id=i, subset='train', image=np.array([i])) + for i in range(5) + ], categories=['label']) class TestLauncher(Launcher): def launch(self, inputs): for i, inp in enumerate(inputs): - yield [ Label(attributes={'idx': i, 'data': inp.item()}) ] + yield [ Label(0, attributes={'idx': i, 'data': inp.item()}) ] model_name = 'model' launcher_name = 'custom_launcher' @@ -150,10 +150,9 @@ class ProjectTest(TestCase): project.env.launchers.register(launcher_name, TestLauncher) project.add_model(model_name, { 'launcher': launcher_name }) model = project.make_executable_model(model_name) - extractor = TestExtractor() batch_size = 3 - executor = InferenceWrapper(extractor, model, batch_size=batch_size) + executor = ModelTransform(dataset, model, batch_size=batch_size) for item in executor: self.assertEqual(1, len(item.annotations)) @@ -169,17 +168,17 @@ class ProjectTest(TestCase): yield DatasetItem(id=i, image=np.ones([2, 2, 3]) * i, annotations=[Label(i)]) + def categories(self): + label_cat = LabelCategories() + label_cat.add('0') + label_cat.add('1') + return { AnnotationType.label: label_cat } + class TestLauncher(Launcher): def launch(self, inputs): for inp in inputs: yield [ Label(inp[0, 0, 0]) ] - class TestConverter(Converter): - def __call__(self, extractor, save_dir): - for item in extractor: - with open(osp.join(save_dir, '%s.txt' % item.id), 'w') as f: - f.write(str(item.annotations[0].label) + '\n') - class TestExtractorDst(Extractor): def __init__(self, url): super().__init__() @@ -199,7 +198,6 @@ class ProjectTest(TestCase): project = Project() project.env.launchers.register(launcher_name, TestLauncher) project.env.extractors.register(extractor_name, TestExtractorSrc) - project.env.converters.register(extractor_name, TestConverter) project.add_model(model_name, { 'launcher': launcher_name }) project.add_source('source', { 'format': extractor_name }) diff --git a/datumaro/tests/test_tfrecord_format.py b/datumaro/tests/test_tfrecord_format.py index 737ea6cf..f2dbd160 100644 --- a/datumaro/tests/test_tfrecord_format.py +++ b/datumaro/tests/test_tfrecord_format.py @@ -1,17 +1,37 @@ +from functools import partial import numpy as np +import os.path as osp -from unittest import TestCase - +from unittest import TestCase, skipIf +from datumaro.components.project import Dataset from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Bbox, Mask, LabelCategories ) -from datumaro.plugins.tf_detection_api_format.importer import TfDetectionApiImporter -from datumaro.plugins.tf_detection_api_format.extractor import TfDetectionApiExtractor -from datumaro.plugins.tf_detection_api_format.converter import TfDetectionApiConverter +from datumaro.components.project import Project from datumaro.util.image import Image from datumaro.util.test_utils import TestDir, compare_datasets - - +from datumaro.util.tf_util import check_import + +try: + from datumaro.plugins.tf_detection_api_format.importer import TfDetectionApiImporter + from datumaro.plugins.tf_detection_api_format.extractor import TfDetectionApiExtractor + from datumaro.plugins.tf_detection_api_format.converter import TfDetectionApiConverter + import_failed = False +except ImportError: + import_failed = True + + import importlib + module_found = importlib.util.find_spec('tensorflow') is not None + + @skipIf(not module_found, "Tensorflow package is not found") + class TfImportTest(TestCase): + def test_raises_when_crashes_on_import(self): + # Should fire if import can't be done for any reason except + # module unavailability and import crash + with self.assertRaisesRegex(ImportError, 'Test process exit code'): + check_import() + +@skipIf(import_failed, "Failed to import tensorflow") class TfrecordConverterTest(TestCase): def _test_save_and_load(self, source_dataset, converter, test_dir, target_dataset=None, importer_args=None): @@ -28,122 +48,97 @@ class TfrecordConverterTest(TestCase): compare_datasets(self, expected=target_dataset, actual=parsed_dataset) def test_can_save_bboxes(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(0, 4, 4, 8, label=2), - Bbox(0, 4, 4, 4, label=3), - Bbox(2, 4, 4, 4), - ] - ), - - DatasetItem(id=2, subset='val', - image=np.ones((8, 8, 3)), - annotations=[ - Bbox(1, 2, 4, 2, label=3), - ] - ), - - DatasetItem(id=3, subset='test', - image=np.ones((5, 4, 3)) * 3, - ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } + test_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2), + Bbox(0, 4, 4, 4, label=3), + Bbox(2, 4, 4, 4), + ], attributes={'source_id': ''} + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) with TestDir() as test_dir: self._test_save_and_load( - TestExtractor(), TfDetectionApiConverter(save_images=True), + test_dataset, + partial(TfDetectionApiConverter.convert, save_images=True), test_dir) def test_can_save_masks(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', image=np.ones((4, 5, 3)), - annotations=[ - Mask(image=np.array([ - [1, 0, 0, 1], - [0, 1, 1, 0], - [0, 1, 1, 0], - [1, 0, 0, 1], - ]), label=1), - ] - ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } + test_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', image=np.ones((4, 5, 3)), + annotations=[ + Mask(image=np.array([ + [1, 0, 0, 1], + [0, 1, 1, 0], + [0, 1, 1, 0], + [1, 0, 0, 1], + ]), label=1), + ], + attributes={'source_id': ''} + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) with TestDir() as test_dir: self._test_save_and_load( - TestExtractor(), TfDetectionApiConverter(save_masks=True), + test_dataset, + partial(TfDetectionApiConverter.convert, save_masks=True), test_dir) def test_can_save_dataset_with_no_subsets(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(2, 1, 4, 4, label=2), - Bbox(4, 2, 8, 4, label=3), - ] - ), - - DatasetItem(id=2, - image=np.ones((8, 8, 3)) * 2, - annotations=[ - Bbox(4, 4, 4, 4, label=3), - ] - ), - - DatasetItem(id=3, - image=np.ones((8, 4, 3)) * 3, - ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } + test_dataset = Dataset.from_iterable([ + DatasetItem(id=1, + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(2, 1, 4, 4, label=2), + Bbox(4, 2, 8, 4, label=3), + ], + attributes={'source_id': ''} + ), + + DatasetItem(id=2, + image=np.ones((8, 8, 3)) * 2, + annotations=[ + Bbox(4, 4, 4, 4, label=3), + ], + attributes={'source_id': ''} + ), + + DatasetItem(id=3, + image=np.ones((8, 4, 3)) * 3, + attributes={'source_id': ''} + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) with TestDir() as test_dir: self._test_save_and_load( - TestExtractor(), TfDetectionApiConverter(save_images=True), + test_dataset, + partial(TfDetectionApiConverter.convert, save_images=True), test_dir) def test_can_save_dataset_with_image_info(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=Image(path='1/q.e', size=(10, 15))), - ]) - - def categories(self): - return { AnnotationType.label: LabelCategories() } + test_dataset = Dataset.from_iterable([ + DatasetItem(id='1/q.e', + image=Image(path='1/q.e', size=(10, 15)), + attributes={'source_id': ''} + ) + ], categories={ + AnnotationType.label: LabelCategories(), + }) with TestDir() as test_dir: - self._test_save_and_load(TestExtractor(), - TfDetectionApiConverter(), test_dir) + self._test_save_and_load(test_dataset, + TfDetectionApiConverter.convert, test_dir) def test_labelmap_parsing(self): text = """ @@ -171,31 +166,45 @@ class TfrecordConverterTest(TestCase): self.assertEqual(expected, parsed) + +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), + 'assets', 'tf_detection_api_dataset') + +@skipIf(import_failed, "Failed to import tensorflow") class TfrecordImporterTest(TestCase): def test_can_detect(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=np.ones((16, 16, 3)), - annotations=[ - Bbox(0, 4, 4, 8, label=2), - ] - ), - ]) - - def categories(self): - label_cat = LabelCategories() - for label in range(10): - label_cat.add('label_' + str(label)) - return { - AnnotationType.label: label_cat, - } - - def generate_dummy_tfrecord(path): - TfDetectionApiConverter()(TestExtractor(), save_dir=path) - - with TestDir() as test_dir: - generate_dummy_tfrecord(test_dir) + self.assertTrue(TfDetectionApiImporter.detect(DUMMY_DATASET_DIR)) + + def test_can_import(self): + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=np.ones((16, 16, 3)), + annotations=[ + Bbox(0, 4, 4, 8, label=2), + Bbox(0, 4, 4, 4, label=3), + Bbox(2, 4, 4, 4), + ], + attributes={'source_id': '1'} + ), + + DatasetItem(id=2, subset='val', + image=np.ones((8, 8, 3)), + annotations=[ + Bbox(1, 2, 4, 2, label=3), + ], + attributes={'source_id': '2'} + ), + + DatasetItem(id=3, subset='test', + image=np.ones((5, 4, 3)) * 3, + attributes={'source_id': '3'} + ), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(label) for label in range(10)), + }) + + dataset = Project.import_from(DUMMY_DATASET_DIR, 'tf_detection_api') \ + .make_dataset() - self.assertTrue(TfDetectionApiImporter.detect(test_dir)) \ No newline at end of file + compare_datasets(self, target_dataset, dataset) diff --git a/datumaro/tests/test_transforms.py b/datumaro/tests/test_transforms.py index 522cf2b5..ed072a67 100644 --- a/datumaro/tests/test_transforms.py +++ b/datumaro/tests/test_transforms.py @@ -2,7 +2,7 @@ import logging as log import numpy as np from unittest import TestCase - +from datumaro.components.project import Dataset from datumaro.components.extractor import (Extractor, DatasetItem, Mask, Polygon, PolyLine, Points, Bbox, Label, LabelCategories, MaskCategories, AnnotationType @@ -67,304 +67,269 @@ class TransformsTest(TestCase): compare_datasets(self, DstExtractor(), actual) def test_mask_to_polygons_small_polygons_message(self): - class SrcExtractor(Extractor): - def __iter__(self): - items = [ - DatasetItem(id=1, image=np.zeros((5, 10, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 0], - [0, 1, 0], - [0, 0, 0], - ]), - ), - ] + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0], + ]), ), ] - return iter(items) + ), + ]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ DatasetItem(id=1, image=np.zeros((5, 10, 3))), ]) + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 10, 3))), ]) with self.assertLogs(level=log.DEBUG) as logs: - actual = transforms.MasksToPolygons(SrcExtractor()) + actual = transforms.MasksToPolygons(source_dataset) - compare_datasets(self, DstExtractor(), actual) + compare_datasets(self, target_dataset, actual) self.assertRegex('\n'.join(logs.output), 'too small polygons') def test_polygons_to_masks(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 10, 3)), - annotations=[ - Polygon([0, 0, 4, 0, 4, 4]), - Polygon([5, 0, 9, 0, 5, 5]), - ] - ), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Polygon([0, 0, 4, 0, 4, 4]), + Polygon([5, 0, 9, 0, 5, 5]), + ] + ), + ]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 10, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 0, 0, 0, 1, 1, 1, 1, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]), - ), - Mask(np.array([ - [0, 1, 1, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]), - ), - ] + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 0, 0, 0, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), ), - ]) + Mask(np.array([ + [0, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]), + ), + ] + ), + ]) - actual = transforms.PolygonsToMasks(SrcExtractor()) - compare_datasets(self, DstExtractor(), actual) + actual = transforms.PolygonsToMasks(source_dataset) + compare_datasets(self, target_dataset, actual) def test_crop_covered_segments(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - # The mask is partially covered by the polygon - Mask(np.array([ - [0, 0, 1, 1, 1], - [0, 0, 1, 1, 1], - [1, 1, 1, 1, 1], - [1, 1, 1, 0, 0], - [1, 1, 1, 0, 0]], - ), - z_order=0), - Polygon([1, 1, 4, 1, 4, 4, 1, 4], - z_order=1), - ] - ), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + # The mask is partially covered by the polygon + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + z_order=1), + ] + ), + ]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 1, 1, 1], - [0, 0, 0, 0, 1], - [1, 0, 0, 0, 1], - [1, 0, 0, 0, 0], - [1, 1, 1, 0, 0]], - ), - z_order=0), - Polygon([1, 1, 4, 1, 4, 4, 1, 4], - z_order=1), - ] - ), - ]) + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + z_order=1), + ] + ), + ]) - actual = transforms.CropCoveredSegments(SrcExtractor()) - compare_datasets(self, DstExtractor(), actual) + actual = transforms.CropCoveredSegments(source_dataset) + compare_datasets(self, target_dataset, actual) def test_merge_instance_segments(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 1, 1, 1], - [0, 0, 0, 0, 1], - [1, 0, 0, 0, 1], - [1, 0, 0, 0, 0], - [1, 1, 1, 0, 0]], - ), - z_order=0, group=1), - Polygon([1, 1, 4, 1, 4, 4, 1, 4], - z_order=1, group=1), - Polygon([0, 0, 0, 2, 2, 2, 2, 0], - z_order=1), - ] - ), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0, group=1), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], + z_order=1, group=1), + Polygon([0, 0, 0, 2, 2, 2, 2, 0], + z_order=1), + ] + ), + ]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 1, 1, 1], - [0, 1, 1, 1, 1], - [1, 1, 1, 1, 1], - [1, 1, 1, 1, 0], - [1, 1, 1, 0, 0]], - ), - z_order=0, group=1), - Mask(np.array([ - [1, 1, 0, 0, 0], - [1, 1, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0]], - ), - z_order=1), - ] - ), - ]) + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 0], + [1, 1, 1, 0, 0]], + ), + z_order=0, group=1), + Mask(np.array([ + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=1), + ] + ), + ]) - actual = transforms.MergeInstanceSegments(SrcExtractor(), + actual = transforms.MergeInstanceSegments(source_dataset, include_polygons=True) - compare_datasets(self, DstExtractor(), actual) + compare_datasets(self, target_dataset, actual) def test_map_subsets(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='a'), - DatasetItem(id=2, subset='b'), - DatasetItem(id=3, subset='c'), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='a'), + DatasetItem(id=2, subset='b'), + DatasetItem(id=3, subset='c'), + ]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset=''), - DatasetItem(id=2, subset='a'), - DatasetItem(id=3, subset='c'), - ]) + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset=''), + DatasetItem(id=2, subset='a'), + DatasetItem(id=3, subset='c'), + ]) - actual = transforms.MapSubsets(SrcExtractor(), + actual = transforms.MapSubsets(source_dataset, { 'a': '', 'b': 'a' }) - compare_datasets(self, DstExtractor(), actual) + compare_datasets(self, target_dataset, actual) def test_shapes_to_boxes(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Mask(np.array([ - [0, 0, 1, 1, 1], - [0, 0, 0, 0, 1], - [1, 0, 0, 0, 1], - [1, 0, 0, 0, 0], - [1, 1, 1, 0, 0]], - ), id=1), - Polygon([1, 1, 4, 1, 4, 4, 1, 4], id=2), - PolyLine([1, 1, 2, 1, 2, 2, 1, 2], id=3), - Points([2, 2, 4, 2, 4, 4, 2, 4], id=4), - ] - ), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [1, 1, 1, 0, 0]], + ), id=1), + Polygon([1, 1, 4, 1, 4, 4, 1, 4], id=2), + PolyLine([1, 1, 2, 1, 2, 2, 1, 2], id=3), + Points([2, 2, 4, 2, 4, 4, 2, 4], id=4), + ] + ), + ]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Bbox(0, 0, 4, 4, id=1), - Bbox(1, 1, 3, 3, id=2), - Bbox(1, 1, 1, 1, id=3), - Bbox(2, 2, 2, 2, id=4), - ] - ), - ]) + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Bbox(0, 0, 4, 4, id=1), + Bbox(1, 1, 3, 3, id=2), + Bbox(1, 1, 1, 1, id=3), + Bbox(2, 2, 2, 2, id=4), + ] + ), + ]) - actual = transforms.ShapesToBoxes(SrcExtractor()) - compare_datasets(self, DstExtractor(), actual) + actual = transforms.ShapesToBoxes(source_dataset) + compare_datasets(self, target_dataset, actual) def test_id_from_image(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image='path.jpg'), - DatasetItem(id=2), - ]) - - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id='path', image='path.jpg'), - DatasetItem(id=2), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image='path.jpg'), + DatasetItem(id=2), + ]) + target_dataset = Dataset.from_iterable([ + DatasetItem(id='path', image='path.jpg'), + DatasetItem(id=2), + ]) - actual = transforms.IdFromImageName(SrcExtractor()) - compare_datasets(self, DstExtractor(), actual) + actual = transforms.IdFromImageName(source_dataset) + compare_datasets(self, target_dataset, actual) def test_boxes_to_masks(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Bbox(0, 0, 3, 3, z_order=1), - Bbox(0, 0, 3, 1, z_order=2), - Bbox(0, 2, 3, 1, z_order=3), - ] - ), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Bbox(0, 0, 3, 3, z_order=1), + Bbox(0, 0, 3, 1, z_order=2), + Bbox(0, 2, 3, 1, z_order=3), + ] + ), + ]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, image=np.zeros((5, 5, 3)), - annotations=[ - Mask(np.array([ - [1, 1, 1, 0, 0], - [1, 1, 1, 0, 0], - [1, 1, 1, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0]], - ), - z_order=1), - Mask(np.array([ - [1, 1, 1, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0]], - ), - z_order=2), - Mask(np.array([ - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [1, 1, 1, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0]], - ), - z_order=3), - ] - ), - ]) + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1, image=np.zeros((5, 5, 3)), + annotations=[ + Mask(np.array([ + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=1), + Mask(np.array([ + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=2), + Mask(np.array([ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [1, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], + ), + z_order=3), + ] + ), + ]) - actual = transforms.BoxesToMasks(SrcExtractor()) - compare_datasets(self, DstExtractor(), actual) + actual = transforms.BoxesToMasks(source_dataset) + compare_datasets(self, target_dataset, actual) def test_random_split(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset="a"), - DatasetItem(id=2, subset="a"), - DatasetItem(id=3, subset="b"), - DatasetItem(id=4, subset="b"), - DatasetItem(id=5, subset="b"), - DatasetItem(id=6, subset=""), - DatasetItem(id=7, subset=""), - ]) + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset="a"), + DatasetItem(id=2, subset="a"), + DatasetItem(id=3, subset="b"), + DatasetItem(id=4, subset="b"), + DatasetItem(id=5, subset="b"), + DatasetItem(id=6, subset=""), + DatasetItem(id=7, subset=""), + ]) - actual = transforms.RandomSplit(SrcExtractor(), splits=[ + actual = transforms.RandomSplit(source_dataset, splits=[ ('train', 4.0 / 7.0), ('test', 3.0 / 7.0), ]) @@ -373,113 +338,78 @@ class TransformsTest(TestCase): self.assertEqual(3, len(actual.get_subset('test'))) def test_random_split_gives_error_on_wrong_ratios(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([DatasetItem(id=1)]) + source_dataset = Dataset.from_iterable([DatasetItem(id=1)]) with self.assertRaises(Exception): - transforms.RandomSplit(SrcExtractor(), splits=[ + transforms.RandomSplit(source_dataset, splits=[ ('train', 0.5), ('test', 0.7), ]) with self.assertRaises(Exception): - transforms.RandomSplit(SrcExtractor(), splits=[]) + transforms.RandomSplit(source_dataset, splits=[]) with self.assertRaises(Exception): - transforms.RandomSplit(SrcExtractor(), splits=[ + transforms.RandomSplit(source_dataset, splits=[ ('train', -0.5), ('test', 1.5), ]) def test_remap_labels(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, annotations=[ - # Should be remapped - Label(1), - Bbox(1, 2, 3, 4, label=2), - Mask(image=np.array([1]), label=3), - - # Should be kept - Polygon([1, 1, 2, 2, 3, 4], label=4), - PolyLine([1, 3, 4, 2, 5, 6], label=None) - ]), - ]) - - def categories(self): - label_cat = LabelCategories() - label_cat.add('label0') - label_cat.add('label1') - label_cat.add('label2') - label_cat.add('label3') - label_cat.add('label4') - - mask_cat = MaskCategories( - colormap=mask_tools.generate_colormap(5)) - - return { - AnnotationType.label: label_cat, - AnnotationType.mask: mask_cat, - } - - class DstExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, annotations=[ - Label(1), - Bbox(1, 2, 3, 4, label=0), - Mask(image=np.array([1]), label=1), - - Polygon([1, 1, 2, 2, 3, 4], label=2), - PolyLine([1, 3, 4, 2, 5, 6], label=None) - ]), - ]) - - def categories(self): - label_cat = LabelCategories() - label_cat.add('label0') - label_cat.add('label9') - label_cat.add('label4') - - mask_cat = MaskCategories(colormap={ - k: v for k, v in mask_tools.generate_colormap(5).items() - if k in { 0, 1, 3, 4 } - }) - - return { - AnnotationType.label: label_cat, - AnnotationType.mask: mask_cat, - } - - actual = transforms.RemapLabels(SrcExtractor(), mapping={ + src_dataset = Dataset.from_iterable([ + DatasetItem(id=1, annotations=[ + # Should be remapped + Label(1), + Bbox(1, 2, 3, 4, label=2), + Mask(image=np.array([1]), label=3), + + # Should be kept + Polygon([1, 1, 2, 2, 3, 4], label=4), + PolyLine([1, 3, 4, 2, 5, 6]) + ]) + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label%s' % i for i in range(5)), + AnnotationType.mask: MaskCategories( + colormap=mask_tools.generate_colormap(5)), + }) + + dst_dataset = Dataset.from_iterable([ + DatasetItem(id=1, annotations=[ + Label(1), + Bbox(1, 2, 3, 4, label=0), + Mask(image=np.array([1]), label=1), + + Polygon([1, 1, 2, 2, 3, 4], label=2), + PolyLine([1, 3, 4, 2, 5, 6], label=None) + ]), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + ['label0', 'label9', 'label4']), + AnnotationType.mask: MaskCategories(colormap={ + k: v for k, v in mask_tools.generate_colormap(5).items() + if k in { 0, 1, 3, 4 } + }) + }) + + actual = transforms.RemapLabels(src_dataset, mapping={ 'label1': 'label9', 'label2': 'label0', 'label3': 'label9', }, default='keep') - compare_datasets(self, DstExtractor(), actual) + compare_datasets(self, dst_dataset, actual) def test_remap_labels_delete_unspecified(self): - class SrcExtractor(Extractor): - def __iter__(self): - return iter([ DatasetItem(id=1, annotations=[ Label(0) ]) ]) - - def categories(self): - label_cat = LabelCategories() - label_cat.add('label0') + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, annotations=[ Label(0) ]) + ], categories=['label0']) - return { AnnotationType.label: label_cat } + target_dataset = Dataset.from_iterable([ + DatasetItem(id=1), + ], categories=[]) - class DstExtractor(Extractor): - def __iter__(self): - return iter([ DatasetItem(id=1, annotations=[]) ]) - - def categories(self): - return { AnnotationType.label: LabelCategories() } - - actual = transforms.RemapLabels(SrcExtractor(), + actual = transforms.RemapLabels(source_dataset, mapping={}, default='delete') - compare_datasets(self, DstExtractor(), actual) + compare_datasets(self, target_dataset, actual) diff --git a/datumaro/tests/test_voc_format.py b/datumaro/tests/test_voc_format.py index 9703acaa..e83a7430 100644 --- a/datumaro/tests/test_voc_format.py +++ b/datumaro/tests/test_voc_format.py @@ -1,9 +1,7 @@ from collections import OrderedDict +from functools import partial import numpy as np -import os import os.path as osp -from xml.etree import ElementTree as ET -import shutil from unittest import TestCase @@ -11,13 +9,6 @@ from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Label, Bbox, Mask, LabelCategories, ) import datumaro.plugins.voc_format.format as VOC -from datumaro.plugins.voc_format.extractor import ( - VocClassificationExtractor, - VocDetectionExtractor, - VocSegmentationExtractor, - VocLayoutExtractor, - VocActionExtractor, -) from datumaro.plugins.voc_format.converter import ( VocConverter, VocClassificationConverter, @@ -28,11 +19,11 @@ from datumaro.plugins.voc_format.converter import ( ) from datumaro.plugins.voc_format.importer import VocImporter from datumaro.components.project import Project -from datumaro.util.image import save_image, Image +from datumaro.util.image import Image from datumaro.util.test_utils import TestDir, compare_datasets -class VocTest(TestCase): +class VocFormatTest(TestCase): def test_colormap_generator(self): reference = np.array([ [ 0, 0, 0], @@ -61,115 +52,18 @@ class VocTest(TestCase): self.assertTrue(np.array_equal(reference, list(VOC.VocColormap.values()))) -def get_label(extractor, label_id): - return extractor.categories()[AnnotationType.label].items[label_id].name - -def generate_dummy_voc(path): - cls_subsets_dir = osp.join(path, 'ImageSets', 'Main') - action_subsets_dir = osp.join(path, 'ImageSets', 'Action') - layout_subsets_dir = osp.join(path, 'ImageSets', 'Layout') - segm_subsets_dir = osp.join(path, 'ImageSets', 'Segmentation') - ann_dir = osp.join(path, 'Annotations') - img_dir = osp.join(path, 'JPEGImages') - segm_dir = osp.join(path, 'SegmentationClass') - inst_dir = osp.join(path, 'SegmentationObject') - - os.makedirs(cls_subsets_dir) - os.makedirs(ann_dir) - os.makedirs(img_dir) - os.makedirs(segm_dir) - os.makedirs(inst_dir) - - subsets = { - 'train': ['2007_000001'], - 'test': ['2007_000002'], - } - - # Subsets - for subset_name, subset in subsets.items(): - for item in subset: - with open(osp.join(cls_subsets_dir, subset_name + '.txt'), 'w') as f: - for item in subset: - f.write('%s\n' % item) - shutil.copytree(cls_subsets_dir, action_subsets_dir) - shutil.copytree(cls_subsets_dir, layout_subsets_dir) - shutil.copytree(cls_subsets_dir, segm_subsets_dir) - - # Classification - subset_name = 'train' - subset = subsets[subset_name] - for label in VOC.VocLabel: - with open(osp.join(cls_subsets_dir, '%s_%s.txt' % \ - (label.name, subset_name)), 'w') as f: - for item in subset: - presence = label.value % 2 - f.write('%s %2d\n' % (item, 1 if presence else -1)) - - # Detection + Action + Layout - subset_name = 'train' - subset = subsets[subset_name] - for item in subset: - root_elem = ET.Element('annotation') - ET.SubElement(root_elem, 'folder').text = 'VOC' + item.split('_')[0] - ET.SubElement(root_elem, 'filename').text = item + '.jpg' - - size_elem = ET.SubElement(root_elem, 'size') - ET.SubElement(size_elem, 'width').text = '10' - ET.SubElement(size_elem, 'height').text = '20' - ET.SubElement(size_elem, 'depth').text = '3' - - ET.SubElement(root_elem, 'segmented').text = '1' - - obj1_elem = ET.SubElement(root_elem, 'object') - ET.SubElement(obj1_elem, 'name').text = 'cat' - ET.SubElement(obj1_elem, 'pose').text = VOC.VocPose(1).name - ET.SubElement(obj1_elem, 'truncated').text = '1' - ET.SubElement(obj1_elem, 'difficult').text = '0' - obj1bb_elem = ET.SubElement(obj1_elem, 'bndbox') - ET.SubElement(obj1bb_elem, 'xmin').text = '1' - ET.SubElement(obj1bb_elem, 'ymin').text = '2' - ET.SubElement(obj1bb_elem, 'xmax').text = '3' - ET.SubElement(obj1bb_elem, 'ymax').text = '4' - - obj2_elem = ET.SubElement(root_elem, 'object') - ET.SubElement(obj2_elem, 'name').text = 'person' - obj2bb_elem = ET.SubElement(obj2_elem, 'bndbox') - ET.SubElement(obj2bb_elem, 'xmin').text = '4' - ET.SubElement(obj2bb_elem, 'ymin').text = '5' - ET.SubElement(obj2bb_elem, 'xmax').text = '6' - ET.SubElement(obj2bb_elem, 'ymax').text = '7' - obj2head_elem = ET.SubElement(obj2_elem, 'part') - ET.SubElement(obj2head_elem, 'name').text = VOC.VocBodyPart(1).name - obj2headbb_elem = ET.SubElement(obj2head_elem, 'bndbox') - ET.SubElement(obj2headbb_elem, 'xmin').text = '5.5' - ET.SubElement(obj2headbb_elem, 'ymin').text = '6' - ET.SubElement(obj2headbb_elem, 'xmax').text = '7.5' - ET.SubElement(obj2headbb_elem, 'ymax').text = '8' - obj2act_elem = ET.SubElement(obj2_elem, 'actions') - for act in VOC.VocAction: - ET.SubElement(obj2act_elem, act.name).text = '%s' % (act.value % 2) - - with open(osp.join(ann_dir, item + '.xml'), 'w') as f: - f.write(ET.tostring(root_elem, encoding='unicode')) - - # Segmentation + Instances - subset_name = 'train' - subset = subsets[subset_name] - for item in subset: - save_image(osp.join(segm_dir, item + '.png'), - np.tile(VOC.VocColormap[2][::-1], (5, 10, 1)) - ) - save_image(osp.join(inst_dir, item + '.png'), - np.tile(1, (5, 10, 1))) - - # Test images - subset_name = 'test' - subset = subsets[subset_name] - for item in subset: - save_image(osp.join(img_dir, item + '.jpg'), - np.ones([10, 20, 3])) - - return subsets + def test_can_write_and_parse_labelmap(self): + src_label_map = VOC.make_voc_label_map() + src_label_map['qq'] = [None, ['part1', 'part2'], ['act1', 'act2']] + src_label_map['ww'] = [(10, 20, 30), [], ['act3']] + + with TestDir() as test_dir: + file_path = osp.join(test_dir, 'test.txt') + + VOC.write_label_map(file_path, src_label_map) + dst_label_map = VOC.parse_label_map(file_path) + + self.assertEqual(src_label_map, dst_label_map) class TestExtractorBase(Extractor): def _label(self, voc_label): @@ -178,32 +72,20 @@ class TestExtractorBase(Extractor): def categories(self): return VOC.make_voc_categories() -class VocExtractorTest(TestCase): - def test_can_load_voc_cls(self): - class DstExtractor(TestExtractorBase): - def __iter__(self): - return iter([ - DatasetItem(id='2007_000001', subset='train', - annotations=[ - Label(self._label(l.name)) - for l in VOC.VocLabel if l.value % 2 == 1 - ] - ), - ]) - - with TestDir() as test_dir: - generate_dummy_voc(test_dir) - parsed_train = VocClassificationExtractor( - osp.join(test_dir, 'ImageSets', 'Main', 'train.txt')) - compare_datasets(self, DstExtractor(), parsed_train) +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'voc_dataset') - def test_can_load_voc_det(self): +class VocImportTest(TestCase): + def test_can_import(self): class DstExtractor(TestExtractorBase): def __iter__(self): return iter([ DatasetItem(id='2007_000001', subset='train', + image=Image(path='2007_000001.jpg', size=(20, 10)), annotations=[ + Label(self._label(l.name)) + for l in VOC.VocLabel if l.value % 2 == 1 + ] + [ Bbox(1, 2, 2, 2, label=self._label('cat'), attributes={ 'pose': VOC.VocPose(1).name, @@ -224,102 +106,27 @@ class VocExtractorTest(TestCase): } }, id=2, group=2, - # TODO: Actions and group should be excluded - # as soon as correct merge is implemented ), - ] - ), - ]) - - with TestDir() as test_dir: - generate_dummy_voc(test_dir) - parsed_train = VocDetectionExtractor( - osp.join(test_dir, 'ImageSets', 'Main', 'train.txt')) - compare_datasets(self, DstExtractor(), parsed_train) - - def test_can_load_voc_segm(self): - class DstExtractor(TestExtractorBase): - def __iter__(self): - return iter([ - DatasetItem(id='2007_000001', subset='train', - annotations=[ + Bbox(5.5, 6, 2, 2, label=self._label( + VOC.VocBodyPart(1).name), + group=2 + ), Mask(image=np.ones([5, 10]), label=self._label(VOC.VocLabel(2).name), group=1, ), ] ), + DatasetItem(id='2007_000002', subset='test', + image=np.zeros((20, 10, 3))), ]) - with TestDir() as test_dir: - generate_dummy_voc(test_dir) - parsed_train = VocSegmentationExtractor( - osp.join(test_dir, 'ImageSets', 'Segmentation', 'train.txt')) - compare_datasets(self, DstExtractor(), parsed_train) + dataset = Project.import_from(DUMMY_DATASET_DIR, 'voc').make_dataset() - def test_can_load_voc_layout(self): - class DstExtractor(TestExtractorBase): - def __iter__(self): - return iter([ - DatasetItem(id='2007_000001', subset='train', - annotations=[ - Bbox(4, 5, 2, 2, label=self._label('person'), - attributes={ - 'truncated': False, - 'difficult': False, - 'occluded': False, - **{ - a.name: a.value % 2 == 1 - for a in VOC.VocAction - } - }, - id=2, group=2, - # TODO: Actions should be excluded - # as soon as correct merge is implemented - ), - Bbox(5.5, 6, 2, 2, label=self._label( - VOC.VocBodyPart(1).name), - group=2 - ) - ] - ), - ]) - - with TestDir() as test_dir: - generate_dummy_voc(test_dir) - parsed_train = VocLayoutExtractor( - osp.join(test_dir, 'ImageSets', 'Layout', 'train.txt')) - compare_datasets(self, DstExtractor(), parsed_train) - - def test_can_load_voc_action(self): - class DstExtractor(TestExtractorBase): - def __iter__(self): - return iter([ - DatasetItem(id='2007_000001', subset='train', - annotations=[ - Bbox(4, 5, 2, 2, label=self._label('person'), - attributes={ - 'truncated': False, - 'difficult': False, - 'occluded': False, - **{ - a.name: a.value % 2 == 1 - for a in VOC.VocAction - } - # TODO: group should be excluded - # as soon as correct merge is implemented - }, - id=2, group=2, - ), - ] - ), - ]) + compare_datasets(self, DstExtractor(), dataset) - with TestDir() as test_dir: - generate_dummy_voc(test_dir) - parsed_train = VocActionExtractor( - osp.join(test_dir, 'ImageSets', 'Action', 'train.txt')) - compare_datasets(self, DstExtractor(), parsed_train) + def test_can_detect_voc(self): + self.assertTrue(VocImporter.detect(DUMMY_DATASET_DIR)) class VocConverterTest(TestCase): def _test_save_and_load(self, source_dataset, converter, test_dir, @@ -339,7 +146,7 @@ class VocConverterTest(TestCase): class TestExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=0, subset='a', annotations=[ + DatasetItem(id='a/0', subset='a', annotations=[ Label(1), Label(2), Label(3), @@ -352,13 +159,14 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocClassificationConverter(label_map='voc'), test_dir) + partial(VocClassificationConverter.convert, label_map='voc'), + test_dir) def test_can_save_voc_det(self): class TestExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=1, subset='a', annotations=[ + DatasetItem(id='a/1', subset='a', annotations=[ Bbox(2, 3, 4, 5, label=2, attributes={ 'occluded': True } ), @@ -377,7 +185,7 @@ class VocConverterTest(TestCase): class DstExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=1, subset='a', annotations=[ + DatasetItem(id='a/1', subset='a', annotations=[ Bbox(2, 3, 4, 5, label=2, id=1, group=1, attributes={ 'truncated': False, @@ -407,14 +215,14 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocDetectionConverter(label_map='voc'), test_dir, - target_dataset=DstExtractor()) + partial(VocDetectionConverter.convert, label_map='voc'), + test_dir, target_dataset=DstExtractor()) def test_can_save_voc_segm(self): class TestExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=1, subset='a', annotations=[ + DatasetItem(id='a/b/1', subset='a', annotations=[ # overlapping masks, the first should be truncated # the second and third are different instances Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, @@ -429,7 +237,7 @@ class VocConverterTest(TestCase): class DstExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=1, subset='a', annotations=[ + DatasetItem(id='a/b/1', subset='a', annotations=[ Mask(image=np.array([[0, 0, 1, 0, 0]]), label=4, group=1), Mask(image=np.array([[1, 1, 0, 0, 0]]), label=3, @@ -441,8 +249,8 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocSegmentationConverter(label_map='voc'), test_dir, - target_dataset=DstExtractor()) + partial(VocSegmentationConverter.convert, label_map='voc'), + test_dir, target_dataset=DstExtractor()) def test_can_save_voc_segm_unpainted(self): class TestExtractor(TestExtractorBase): @@ -475,7 +283,8 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocSegmentationConverter(label_map='voc', apply_colormap=False), + partial(VocSegmentationConverter.convert, + label_map='voc', apply_colormap=False), test_dir, target_dataset=DstExtractor()) def test_can_save_voc_segm_with_many_instances(self): @@ -510,14 +319,14 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocSegmentationConverter(label_map='voc'), test_dir, - target_dataset=DstExtractor()) + partial(VocSegmentationConverter.convert, label_map='voc'), + test_dir, target_dataset=DstExtractor()) def test_can_save_voc_layout(self): class TestExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=1, subset='a', annotations=[ + DatasetItem(id='a/b/1', subset='a', annotations=[ Bbox(2, 3, 4, 5, label=2, id=1, group=1, attributes={ 'pose': VOC.VocPose(1).name, @@ -535,13 +344,13 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocLayoutConverter(label_map='voc'), test_dir) + partial(VocLayoutConverter.convert, label_map='voc'), test_dir) def test_can_save_voc_action(self): class TestExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=1, subset='a', annotations=[ + DatasetItem(id='a/b/1', subset='a', annotations=[ Bbox(2, 3, 4, 5, label=2, attributes={ 'truncated': True, @@ -562,7 +371,7 @@ class VocConverterTest(TestCase): class DstExtractor(TestExtractorBase): def __iter__(self): return iter([ - DatasetItem(id=1, subset='a', annotations=[ + DatasetItem(id='a/b/1', subset='a', annotations=[ Bbox(2, 3, 4, 5, label=2, id=1, group=1, attributes={ 'truncated': True, @@ -589,7 +398,8 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocActionConverter(label_map='voc'), test_dir, + partial(VocActionConverter.convert, + label_map='voc', allow_attributes=False), test_dir, target_dataset=DstExtractor()) def test_can_save_dataset_with_no_subsets(self): @@ -608,7 +418,7 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocConverter(label_map='voc'), test_dir) + partial(VocConverter.convert, label_map='voc'), test_dir) def test_can_save_dataset_with_images(self): class TestExtractor(TestExtractorBase): @@ -622,7 +432,8 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocConverter(label_map='voc', save_images=True), test_dir) + partial(VocConverter.convert, label_map='voc', save_images=True), + test_dir) def test_dataset_with_voc_labelmap(self): class SrcExtractor(TestExtractorBase): @@ -657,11 +468,11 @@ class VocConverterTest(TestCase): return VOC.make_voc_categories() with TestDir() as test_dir: - self._test_save_and_load( - SrcExtractor(), VocConverter(label_map='voc'), + self._test_save_and_load(SrcExtractor(), + partial(VocConverter.convert, label_map='voc'), test_dir, target_dataset=DstExtractor()) - def test_dataset_with_guessed_labelmap(self): + def test_dataset_with_source_labelmap_undefined(self): class SrcExtractor(TestExtractorBase): def __iter__(self): yield DatasetItem(id=1, annotations=[ @@ -671,8 +482,8 @@ class VocConverterTest(TestCase): def categories(self): label_cat = LabelCategories() - label_cat.add(VOC.VocLabel(1).name) - label_cat.add('non_voc_label') + label_cat.add('Label_1') + label_cat.add('label_2') return { AnnotationType.label: label_cat, } @@ -680,14 +491,14 @@ class VocConverterTest(TestCase): class DstExtractor(TestExtractorBase): def __iter__(self): yield DatasetItem(id=1, annotations=[ - Bbox(2, 3, 4, 5, label=self._label(VOC.VocLabel(1).name), + Bbox(2, 3, 4, 5, label=self._label('Label_1'), id=1, group=1, attributes={ 'truncated': False, 'difficult': False, 'occluded': False, } ), - Bbox(1, 2, 3, 4, label=self._label('non_voc_label'), + Bbox(1, 2, 3, 4, label=self._label('label_2'), id=2, group=2, attributes={ 'truncated': False, 'difficult': False, @@ -697,32 +508,31 @@ class VocConverterTest(TestCase): ]) def categories(self): - label_map = VOC.make_voc_label_map() - label_map['non_voc_label'] = [None, [], []] - for label_desc in label_map.values(): - label_desc[0] = None # rebuild colormap + label_map = OrderedDict() + label_map['background'] = [None, [], []] + label_map['Label_1'] = [None, [], []] + label_map['label_2'] = [None, [], []] return VOC.make_voc_categories(label_map) with TestDir() as test_dir: - self._test_save_and_load( - SrcExtractor(), VocConverter(label_map='guess'), + self._test_save_and_load(SrcExtractor(), + partial(VocConverter.convert, label_map='source'), test_dir, target_dataset=DstExtractor()) - def test_dataset_with_source_labelmap(self): + def test_dataset_with_source_labelmap_defined(self): class SrcExtractor(TestExtractorBase): def __iter__(self): yield DatasetItem(id=1, annotations=[ Bbox(2, 3, 4, 5, label=0, id=1), - Bbox(1, 2, 3, 4, label=1, id=2), + Bbox(1, 2, 3, 4, label=2, id=2), ]) def categories(self): - label_cat = LabelCategories() - label_cat.add('Label_1') # should become lowercase - label_cat.add('label_2') - return { - AnnotationType.label: label_cat, - } + label_map = OrderedDict() + label_map['label_1'] = [(1, 2, 3), [], []] + label_map['background'] = [(0, 0, 0), [], []] # can be not 0 + label_map['label_2'] = [(3, 2, 1), [], []] + return VOC.make_voc_categories(label_map) class DstExtractor(TestExtractorBase): def __iter__(self): @@ -745,25 +555,25 @@ class VocConverterTest(TestCase): def categories(self): label_map = OrderedDict() - label_map['background'] = [None, [], []] - label_map['label_1'] = [None, [], []] - label_map['label_2'] = [None, [], []] + label_map['background'] = [(0, 0, 0), [], []] + label_map['label_1'] = [(1, 2, 3), [], []] + label_map['label_2'] = [(3, 2, 1), [], []] return VOC.make_voc_categories(label_map) with TestDir() as test_dir: - self._test_save_and_load( - SrcExtractor(), VocConverter(label_map='source'), + self._test_save_and_load(SrcExtractor(), + partial(VocConverter.convert, label_map='source'), test_dir, target_dataset=DstExtractor()) def test_dataset_with_fixed_labelmap(self): class SrcExtractor(TestExtractorBase): def __iter__(self): yield DatasetItem(id=1, annotations=[ - Bbox(2, 3, 4, 5, label=0, id=1), - Bbox(1, 2, 3, 4, label=1, id=2, group=2, + Bbox(2, 3, 4, 5, label=self._label('foreign_label'), id=1), + Bbox(1, 2, 3, 4, label=self._label('label'), id=2, group=2, attributes={'act1': True}), - Bbox(2, 3, 4, 5, label=2, id=3, group=2), - Bbox(2, 3, 4, 6, label=3, id=4, group=2), + Bbox(2, 3, 4, 5, label=self._label('label_part1'), group=2), + Bbox(2, 3, 4, 6, label=self._label('label_part2'), group=2), ]) def categories(self): @@ -776,14 +586,19 @@ class VocConverterTest(TestCase): AnnotationType.label: label_cat, } - label_map = { - 'label': [None, ['label_part1', 'label_part2'], ['act1', 'act2']] - } + label_map = OrderedDict([ + ('label', [None, ['label_part1', 'label_part2'], ['act1', 'act2']]) + ]) + + dst_label_map = OrderedDict([ + ('background', [None, [], []]), + ('label', [None, ['label_part1', 'label_part2'], ['act1', 'act2']]) + ]) class DstExtractor(TestExtractorBase): def __iter__(self): yield DatasetItem(id=1, annotations=[ - Bbox(1, 2, 3, 4, label=0, id=1, group=1, + Bbox(1, 2, 3, 4, label=self._label('label'), id=1, group=1, attributes={ 'act1': True, 'act2': False, @@ -792,16 +607,16 @@ class VocConverterTest(TestCase): 'occluded': False, } ), - Bbox(2, 3, 4, 5, label=1, group=1), - Bbox(2, 3, 4, 6, label=2, group=1), + Bbox(2, 3, 4, 5, label=self._label('label_part1'), group=1), + Bbox(2, 3, 4, 6, label=self._label('label_part2'), group=1), ]) def categories(self): - return VOC.make_voc_categories(label_map) + return VOC.make_voc_categories(dst_label_map) with TestDir() as test_dir: - self._test_save_and_load( - SrcExtractor(), VocConverter(label_map=label_map), + self._test_save_and_load(SrcExtractor(), + partial(VocConverter.convert, label_map=label_map), test_dir, target_dataset=DstExtractor()) def test_can_save_dataset_with_image_info(self): @@ -813,40 +628,50 @@ class VocConverterTest(TestCase): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - VocConverter(label_map='voc'), test_dir) - -class VocImportTest(TestCase): - def test_can_import(self): - with TestDir() as test_dir: - subsets = generate_dummy_voc(test_dir) + partial(VocConverter.convert, label_map='voc'), test_dir) - dataset = Project.import_from(test_dir, 'voc').make_dataset() - - self.assertEqual(len(VOC.VocTask) * len(subsets), - len(dataset.sources)) - self.assertEqual(set(subsets), set(dataset.subsets())) - self.assertEqual( - sum([len(s) for _, s in subsets.items()]), - len(dataset)) + def test_relative_paths(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='1', image=np.ones((4, 2, 3))), + DatasetItem(id='subdir1/1', image=np.ones((2, 6, 3))), + DatasetItem(id='subdir2/1', image=np.ones((5, 4, 3))), + ]) - def test_can_detect_voc(self): with TestDir() as test_dir: - generate_dummy_voc(test_dir) - - dataset_found = VocImporter.detect(test_dir) + self._test_save_and_load(TestExtractor(), + partial(VocConverter.convert, + label_map='voc', save_images=True), + test_dir) - self.assertTrue(dataset_found) + def test_can_save_attributes(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='a', annotations=[ + Bbox(2, 3, 4, 5, label=2, + attributes={ 'occluded': True, 'x': 1, 'y': '2' } + ), + ]), + ]) -class VocFormatTest(TestCase): - def test_can_write_and_parse_labelmap(self): - src_label_map = VOC.make_voc_label_map() - src_label_map['qq'] = [None, ['part1', 'part2'], ['act1', 'act2']] - src_label_map['ww'] = [(10, 20, 30), [], ['act3']] + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='a', annotations=[ + Bbox(2, 3, 4, 5, label=2, id=1, group=1, + attributes={ + 'truncated': False, + 'difficult': False, + 'occluded': True, + 'x': '1', 'y': '2', # can only read strings + } + ), + ]), + ]) with TestDir() as test_dir: - file_path = osp.join(test_dir, 'test.txt') - - VOC.write_label_map(file_path, src_label_map) - dst_label_map = VOC.parse_label_map(file_path) - - self.assertEqual(src_label_map, dst_label_map) + self._test_save_and_load(TestExtractor(), + partial(VocConverter.convert, label_map='voc'), test_dir, + target_dataset=DstExtractor()) diff --git a/datumaro/tests/test_yolo_format.py b/datumaro/tests/test_yolo_format.py index 4d29c349..1f6425d1 100644 --- a/datumaro/tests/test_yolo_format.py +++ b/datumaro/tests/test_yolo_format.py @@ -6,6 +6,7 @@ from unittest import TestCase from datumaro.components.extractor import (Extractor, DatasetItem, AnnotationType, Bbox, LabelCategories, ) +from datumaro.components.project import Project, Dataset from datumaro.plugins.yolo_format.importer import YoloImporter from datumaro.plugins.yolo_format.converter import YoloConverter from datumaro.util.image import Image, save_image @@ -14,70 +15,54 @@ from datumaro.util.test_utils import TestDir, compare_datasets class YoloFormatTest(TestCase): def test_can_save_and_load(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', image=np.ones((8, 8, 3)), - annotations=[ - Bbox(0, 2, 4, 2, label=2), - Bbox(0, 1, 2, 3, label=4), - ]), - DatasetItem(id=2, subset='train', image=np.ones((10, 10, 3)), - annotations=[ - Bbox(0, 2, 4, 2, label=2), - Bbox(3, 3, 2, 3, label=4), - Bbox(2, 1, 2, 3, label=4), - ]), - - DatasetItem(id=3, subset='valid', image=np.ones((8, 8, 3)), - annotations=[ - Bbox(0, 1, 5, 2, label=2), - Bbox(0, 2, 3, 2, label=5), - Bbox(0, 2, 4, 2, label=6), - Bbox(0, 7, 3, 2, label=7), - ]), - ]) - - def categories(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add('label_' + str(i)) - return { - AnnotationType.label: label_categories, - } + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', image=np.ones((8, 8, 3)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(0, 1, 2, 3, label=4), + ]), + DatasetItem(id=2, subset='train', image=np.ones((10, 10, 3)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(3, 3, 2, 3, label=4), + Bbox(2, 1, 2, 3, label=4), + ]), + + DatasetItem(id=3, subset='valid', image=np.ones((8, 8, 3)), + annotations=[ + Bbox(0, 1, 5, 2, label=2), + Bbox(0, 2, 3, 2, label=5), + Bbox(0, 2, 4, 2, label=6), + Bbox(0, 7, 3, 2, label=7), + ]), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(i) for i in range(10)), + }) with TestDir() as test_dir: - source_dataset = TestExtractor() - YoloConverter(save_images=True)(source_dataset, test_dir) + YoloConverter.convert(source_dataset, test_dir, save_images=True) parsed_dataset = YoloImporter()(test_dir).make_dataset() compare_datasets(self, source_dataset, parsed_dataset) def test_can_save_dataset_with_image_info(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=Image(path='1.jpg', size=(10, 15)), - annotations=[ - Bbox(0, 2, 4, 2, label=2), - Bbox(3, 3, 2, 3, label=4), - ]), - ]) - - def categories(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add('label_' + str(i)) - return { - AnnotationType.label: label_categories, - } + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=Image(path='1.jpg', size=(10, 15)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(3, 3, 2, 3, label=4), + ]), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(i) for i in range(10)), + }) with TestDir() as test_dir: - source_dataset = TestExtractor() - YoloConverter()(source_dataset, test_dir) + YoloConverter.convert(source_dataset, test_dir) save_image(osp.join(test_dir, 'obj_train_data', '1.jpg'), np.ones((10, 15, 3))) # put the image for dataset @@ -86,57 +71,70 @@ class YoloFormatTest(TestCase): compare_datasets(self, source_dataset, parsed_dataset) def test_can_load_dataset_with_exact_image_info(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=Image(path='1.jpg', size=(10, 15)), - annotations=[ - Bbox(0, 2, 4, 2, label=2), - Bbox(3, 3, 2, 3, label=4), - ]), - ]) - - def categories(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add('label_' + str(i)) - return { - AnnotationType.label: label_categories, - } + source_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=Image(path='1.jpg', size=(10, 15)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(3, 3, 2, 3, label=4), + ]), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(i) for i in range(10)), + }) with TestDir() as test_dir: - source_dataset = TestExtractor() - YoloConverter()(source_dataset, test_dir) + YoloConverter.convert(source_dataset, test_dir) parsed_dataset = YoloImporter()(test_dir, image_info={'1': (10, 15)}).make_dataset() compare_datasets(self, source_dataset, parsed_dataset) -class YoloImporterTest(TestCase): - def test_can_detect(self): - class TestExtractor(Extractor): - def __iter__(self): - return iter([ - DatasetItem(id=1, subset='train', - image=Image(path='1.jpg', size=(10, 15)), - annotations=[ - Bbox(0, 2, 4, 2, label=2), - Bbox(3, 3, 2, 3, label=4), - ]), - ]) - - def categories(self): - label_categories = LabelCategories() - for i in range(10): - label_categories.add('label_' + str(i)) - return { - AnnotationType.label: label_categories, - } + def test_relative_paths(self): + source_dataset = Dataset.from_iterable([ + DatasetItem(id='1', subset='train', + image=np.ones((4, 2, 3))), + DatasetItem(id='subdir1/1', subset='train', + image=np.ones((2, 6, 3))), + DatasetItem(id='subdir2/1', subset='train', + image=np.ones((5, 4, 3))), + ], categories={ + AnnotationType.label: LabelCategories(), + }) - with TestDir() as test_dir: - YoloConverter()(TestExtractor(), save_dir=test_dir) + for save_images in {True, False}: + with self.subTest(save_images=save_images): + with TestDir() as test_dir: + + YoloConverter.convert(source_dataset, test_dir, + save_images=save_images) + parsed_dataset = YoloImporter()(test_dir).make_dataset() + + compare_datasets(self, source_dataset, parsed_dataset) - self.assertTrue(YoloImporter.detect(test_dir)) \ No newline at end of file + +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'yolo_dataset') + +class YoloImporterTest(TestCase): + def test_can_detect(self): + self.assertTrue(YoloImporter.detect(DUMMY_DATASET_DIR)) + + def test_can_import(self): + expected_dataset = Dataset.from_iterable([ + DatasetItem(id=1, subset='train', + image=np.ones((10, 15, 3)), + annotations=[ + Bbox(0, 2, 4, 2, label=2), + Bbox(3, 3, 2, 3, label=4), + ]), + ], categories={ + AnnotationType.label: LabelCategories.from_iterable( + 'label_' + str(i) for i in range(10)), + }) + + dataset = Project.import_from(DUMMY_DATASET_DIR, 'yolo') \ + .make_dataset() + + compare_datasets(self, expected_dataset, dataset) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index ac3a7c54..76f0804a 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -9,6 +9,8 @@ services: dockerfile: Dockerfile.ci depends_on: - cvat + - cvat_ui + - cvat_proxy environment: COVERALLS_REPO_TOKEN: TRAVIS: diff --git a/docker-compose.yml b/docker-compose.yml index 3bb1eb70..6745493f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: cvat: container_name: cvat - image: cvat + image: cvat/server restart: always depends_on: - cvat_redis @@ -42,17 +42,17 @@ services: args: http_proxy: https_proxy: - no_proxy: + no_proxy: nuclio,${no_proxy} socks_proxy: - TF_ANNOTATION: "no" - AUTO_SEGMENTATION: "no" USER: "django" DJANGO_CONFIGURATION: "production" TZ: "Etc/UTC" - OPENVINO_TOOLKIT: "no" + CLAM_AV: "no" environment: DJANGO_MODWSGI_EXTRA_ARGS: "" ALLOWED_HOSTS: '*' + CVAT_REDIS_HOST: "cvat_redis" + CVAT_POSTGRES_HOST: "cvat_db" volumes: - cvat_data:/home/django/data - cvat_keys:/home/django/keys @@ -61,6 +61,7 @@ services: cvat_ui: container_name: cvat_ui + image: cvat/ui restart: always build: context: . @@ -94,6 +95,32 @@ services: - ./cvat_proxy/conf.d/cvat.conf.template:/etc/nginx/conf.d/cvat.conf.template:ro command: /bin/sh -c "envsubst '$$CVAT_HOST' < /etc/nginx/conf.d/cvat.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" + serverless: + container_name: nuclio + image: quay.io/nuclio/dashboard:1.4.8-amd64 + restart: always + networks: + default: + aliases: + - nuclio + volumes: + - /tmp:/tmp + - /var/run/docker.sock:/var/run/docker.sock + environment: + http_proxy: + https_proxy: + no_proxy: 172.28.0.1,${no_proxy} + NUCLIO_CHECK_FUNCTION_CONTAINERS_HEALTHINESS: "true" + ports: + - "8070:8070" + +networks: + default: + ipam: + config: + - subnet: 172.28.0.0/24 + gateway: 172.28.0.1 + volumes: cvat_db: cvat_data: diff --git a/package.json b/package.json index 6499b0da..16bfea43 100644 --- a/package.json +++ b/package.json @@ -8,27 +8,29 @@ }, "dependencies": {}, "devDependencies": { - "eslint": "^6.8.0", + "eslint": "^7.3.0", "eslint-config-airbnb": "^18.0.1", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-no-unsafe-innerhtml": "^1.0.16", "eslint-plugin-no-unsanitized": "^3.0.2", "eslint-plugin-react": "^7.14.3", - "eslint-plugin-react-hooks": "^1.7.0", + "eslint-plugin-react-hooks": "^4.0.4", "eslint-plugin-security": "^1.4.0", - "remark-lint-emphasis-marker": "^1.0.3", - "remark-lint-list-item-spacing": "^1.1.3", - "remark-lint-maximum-heading-length": "^1.0.3", - "remark-lint-maximum-line-length": "^1.2.1", - "remark-lint-no-dead-urls": "^0.5.0", + "remark-lint-emphasis-marker": "^2.0.0", + "remark-lint-list-item-spacing": "^2.0.0", + "remark-lint-maximum-heading-length": "^2.0.0", + "remark-lint-maximum-line-length": "^2.0.0", + "remark-lint-no-dead-urls": "^1.0.2", "remark-lint-no-file-name-irregular-characters": "^1.0.3", - "remark-lint-ordered-list-marker-style": "^1.0.3", - "remark-lint-strong-marker": "^1.0.3", - "remark-lint-unordered-list-marker-style": "^1.0.3", - "remark-preset-lint-consistent": "^2.0.3", - "remark-preset-lint-markdown-style-guide": "^2.1.3", - "remark-preset-lint-recommended": "^3.0.3" + "remark-lint-ordered-list-marker-style": "^2.0.0", + "remark-lint-strong-marker": "^2.0.0", + "remark-lint-unordered-list-marker-style": "^2.0.0", + "remark-preset-lint-consistent": "^3.0.0", + "remark-preset-lint-markdown-style-guide": "^3.0.0", + "remark-preset-lint-recommended": "^4.0.0", + "stylelint": "^13.6.1", + "stylelint-config-standard": "^20.0.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/serverless/deploy.sh b/serverless/deploy.sh new file mode 100755 index 00000000..9b150a02 --- /dev/null +++ b/serverless/deploy.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +nuctl create project cvat +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio \ + --volume $SCRIPT_DIR/openvino/common:/opt/nuclio/common \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio \ + --volume $SCRIPT_DIR/openvino/common:/opt/nuclio/common \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/openvino/omz/public/yolo-v3-tf/nuclio \ + --volume $SCRIPT_DIR/openvino/common:/opt/nuclio/common \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/openvino/omz/intel/text-detection-0004/nuclio \ + --volume $SCRIPT_DIR/openvino/common:/opt/nuclio/common \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio \ + --volume $SCRIPT_DIR/openvino/common:/opt/nuclio/common \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/openvino/omz/intel/person-reidentification-retail-300/nuclio \ + --volume $SCRIPT_DIR/openvino/common:/opt/nuclio/common \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/openvino/dextr/nuclio \ + --volume $SCRIPT_DIR/openvino/common:/opt/nuclio/common \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/tensorflow/matterport/mask_rcnn/nuclio \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/tensorflow/faster_rcnn_inception_v2_coco/nuclio \ + --platform local + +nuctl deploy --project-name cvat \ + --path $SCRIPT_DIR/pytorch/foolwood/siammask/nuclio \ + --platform local + +nuctl get function diff --git a/serverless/openvino/common/model_loader.py b/serverless/openvino/common/model_loader.py new file mode 100644 index 00000000..ca3354d1 --- /dev/null +++ b/serverless/openvino/common/model_loader.py @@ -0,0 +1,72 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import cv2 +import numpy as np +from openvino.inference_engine import IECore + +class ModelLoader: + def __init__(self, model, weights): + ie_core = IECore() + network = ie_core.read_network(model, weights) + self._network = network + + # Check compatibility + supported_layers = ie_core.query_network(network, "CPU") + not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] + if len(not_supported_layers) != 0: + raise Exception( + "Following layers are not supported by the plugin for specified device {}:\n {}" + .format(ie_core.device, ", ".join(not_supported_layers))) + + # Initialize input blobs + self._input_info_name = None + for blob_name in network.inputs: + if len(network.inputs[blob_name].shape) == 4: + self._input_blob_name = blob_name + elif len(network.inputs[blob_name].shape) == 2: + self._input_info_name = blob_name + else: + raise RuntimeError( + "Unsupported {}D input layer '{}'. Only 2D and 4D input layers are supported" + .format(len(network.inputs[blob_name].shape), blob_name)) + + # Initialize output blob + self._output_blob_name = next(iter(network.outputs)) + + # Load network + self._net = ie_core.load_network(network, "CPU", num_requests=2) + input_type = network.inputs[self._input_blob_name] + self._input_layout = input_type if isinstance(input_type, list) else input_type.shape + + def infer(self, image, preprocessing=True): + image = np.array(image) + _, _, h, w = self._input_layout + if preprocessing: + image = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h)) + if len(image.shape) < 3: # grayscale image + image = image[:, :, np.newaxis] + else: + if image.shape[2] == 4: # the image has alpha channel + image = image[:, :, :3] + + image = image.transpose((2, 0, 1)) # Change data layout from HWC to CHW + + inputs = {self._input_blob_name: image} + if self._input_info_name: + inputs[self._input_info_name] = [h, w, 1] + + results = self._net.infer(inputs) + if len(results) == 1: + return results[self._output_blob_name].copy() + else: + return results.copy() + + def input_size(self): + return self._input_layout[2:] + + @property + def layers(self): + return self._network.layers diff --git a/serverless/openvino/common/python3 b/serverless/openvino/common/python3 new file mode 100755 index 00000000..fca7518d --- /dev/null +++ b/serverless/openvino/common/python3 @@ -0,0 +1,7 @@ +#!/bin/bash + +args=$@ + +. /opt/intel/openvino/bin/setupvars.sh +PYTHONPATH=/opt/nuclio/common:$PYTHONPATH +/usr/bin/python3 $args diff --git a/serverless/openvino/dextr/nuclio/function.yaml b/serverless/openvino/dextr/nuclio/function.yaml new file mode 100644 index 00000000..14b6db3e --- /dev/null +++ b/serverless/openvino/dextr/nuclio/function.yaml @@ -0,0 +1,52 @@ +metadata: + name: openvino.dextr + namespace: cvat + annotations: + name: DEXTR + type: interactor + spec: + framework: openvino + +spec: + description: Deep Extreme Cut + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/common/python3 + + build: + image: cvat/openvino.dextr + baseImage: openvino/ubuntu18_runtime:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + + postCopy: + - kind: RUN + value: curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip + - kind: RUN + value: unzip dextr_model_v1.zip + - kind: RUN + value: pip3 install Pillow + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/openvino/dextr/nuclio/main.py b/serverless/openvino/dextr/nuclio/main.py new file mode 100644 index 00000000..73617002 --- /dev/null +++ b/serverless/openvino/dextr/nuclio/main.py @@ -0,0 +1,26 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler + +def init_context(context): + context.logger.info("Init context... 0%") + + model = ModelHandler() + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("call handler") + data = event.body + points = data["points"] + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + image = Image.open(buf) + + polygon = context.user_data.model.handle(image, points) + return context.Response(body=json.dumps(polygon), + headers={}, + content_type='application/json', + status_code=200) diff --git a/serverless/openvino/dextr/nuclio/model_handler.py b/serverless/openvino/dextr/nuclio/model_handler.py new file mode 100644 index 00000000..9dc74116 --- /dev/null +++ b/serverless/openvino/dextr/nuclio/model_handler.py @@ -0,0 +1,84 @@ +# Copyright (C) 2018-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import cv2 +import numpy as np +import os +from model_loader import ModelLoader + +class ModelHandler: + def __init__(self): + base_dir = os.environ.get("MODEL_PATH", "/opt/nuclio") + model_xml = os.path.join(base_dir, "dextr.xml") + model_bin = os.path.join(base_dir, "dextr.bin") + self.model = ModelLoader(model_xml, model_bin) + + # Input: + # image: PIL image + # points: [[x1,y1], [x2,y2], [x3,y3], [x4,y4], ...] + # Output: + # polygon: [[x1,y1], [x2,y2], [x3,y3], [x4,y4], ...] + def handle(self, image, points): + DEXTR_PADDING = 50 + DEXTR_TRESHOLD = 0.9 + DEXTR_SIZE = 512 + + numpy_image = np.array(image) + points = np.asarray(points, dtype=int) + bounding_box = ( + max(min(points[:, 0]) - DEXTR_PADDING, 0), + max(min(points[:, 1]) - DEXTR_PADDING, 0), + min(max(points[:, 0]) + DEXTR_PADDING, numpy_image.shape[1] - 1), + min(max(points[:, 1]) + DEXTR_PADDING, numpy_image.shape[0] - 1) + ) + + # Prepare an image + numpy_cropped = np.array(image.crop(bounding_box)) + resized = cv2.resize(numpy_cropped, (DEXTR_SIZE, DEXTR_SIZE), + interpolation = cv2.INTER_CUBIC).astype(np.float32) + if len(resized.shape) == 2: # support grayscale images + resized = cv2.cvtColor(resized, cv2.COLOR_GRAY2RGB) + elif resized.shape[2] == 4: # remove alpha channel + resized = resized[:, :, :3] + + # Make a heatmap + points = points - [min(points[:, 0]), min(points[:, 1])] + [DEXTR_PADDING, DEXTR_PADDING] + points = (points * [DEXTR_SIZE / numpy_cropped.shape[1], DEXTR_SIZE / numpy_cropped.shape[0]]).astype(int) + heatmap = np.zeros(shape=resized.shape[:2], dtype=np.float64) + for point in points: + gaussian_x_axis = np.arange(0, DEXTR_SIZE, 1, float) - point[0] + gaussian_y_axis = np.arange(0, DEXTR_SIZE, 1, float)[:, np.newaxis] - point[1] + gaussian = np.exp(-4 * np.log(2) * ((gaussian_x_axis ** 2 + gaussian_y_axis ** 2) / 100)).astype(np.float64) + heatmap = np.maximum(heatmap, gaussian) + cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX) + + # Concat an image and a heatmap + input_dextr = np.concatenate((resized, heatmap[:, :, np.newaxis].astype(resized.dtype)), axis=2) + input_dextr = input_dextr.transpose((2,0,1)) + + pred = self.model.infer(input_dextr[np.newaxis, ...], False)[0, 0, :, :] + pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_CUBIC) + result = np.zeros(numpy_image.shape[:2]) + result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred > DEXTR_TRESHOLD + + # Convert a mask to a polygon + result = np.array(result, dtype=np.uint8) + cv2.normalize(result,result,0,255,cv2.NORM_MINMAX) + contours = None + if int(cv2.__version__.split('.')[0]) > 3: + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[0] + else: + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[1] + + contours = max(contours, key=lambda arr: arr.size) + if contours.shape.count(1): + contours = np.squeeze(contours) + if contours.size < 3 * 2: + raise Exception('Less then three point have been detected. Can not build a polygon.') + + result = [] + for point in contours: + result.append([int(point[0]), int(point[1])]) + + return result diff --git a/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/function.yaml b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/function.yaml new file mode 100644 index 00000000..f4a5d2fb --- /dev/null +++ b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/function.yaml @@ -0,0 +1,48 @@ +metadata: + name: openvino.omz.intel.person-reidentification-retail-0300 + namespace: cvat + annotations: + name: Person reidentification + type: reid + framework: openvino + spec: + +spec: + description: Person reidentification model for a general scenario + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/common/python3 + + build: + image: cvat/openvino.omz.intel.person-reidentification-retail-0300 + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name person-reidentification-retail-0300 -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name person-reidentification-retail-0300 --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/main.py b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/main.py new file mode 100644 index 00000000..9197632a --- /dev/null +++ b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/main.py @@ -0,0 +1,31 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler + +def init_context(context): + context.logger.info("Init context... 0%") + + model = ModelHandler() + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run person-reidentification-retail-0300 model") + data = event.body + buf0 = io.BytesIO(base64.b64decode(data["image0"].encode('utf-8'))) + buf1 = io.BytesIO(base64.b64decode(data["image1"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.5)) + max_distance = float(data.get("max_distance", 50)) + image0 = Image.open(buf0) + image1 = Image.open(buf1) + boxes0 = data["boxes0"] + boxes1 = data["boxes1"] + + results = context.user_data.model.infer(image0, boxes0, + image1, boxes1, threshold, max_distance) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/model_handler.py b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/model_handler.py new file mode 100644 index 00000000..481fa4c4 --- /dev/null +++ b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/model_handler.py @@ -0,0 +1,81 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import math +import numpy +import os +from scipy.optimize import linear_sum_assignment +from scipy.spatial.distance import euclidean, cosine + +from model_loader import ModelLoader + +class ModelHandler: + def __init__(self): + base_dir = os.environ.get("MODEL_PATH", + "/opt/nuclio/open_model_zoo/intel/person-reidentification-retail-0300/FP32") + model_xml = os.path.join(base_dir, "person-reidentification-retail-0300.xml") + model_bin = os.path.join(base_dir, "person-reidentification-retail-0300.bin") + + self.model = ModelLoader(model_xml, model_bin) + + def infer(self, image0, boxes0, image1, boxes1, threshold, distance): + similarity_matrix = self._compute_similarity_matrix(image0, + boxes0, image1, boxes1, distance) + row_idx, col_idx = linear_sum_assignment(similarity_matrix) + results = [-1] * len(boxes0) + for idx0, idx1 in zip(row_idx, col_idx): + if similarity_matrix[idx0, idx1] <= threshold: + results[idx0] = int(idx1) + + return results + + def _match_boxes(self, box0, box1, distance): + cx0 = (box0["points"][0] + box0["points"][2]) / 2 + cy0 = (box0["points"][1] + box0["points"][3]) / 2 + cx1 = (box1["points"][0] + box1["points"][2]) / 2 + cy1 = (box1["points"][1] + box1["points"][3]) / 2 + is_good_distance = euclidean([cx0, cy0], [cx1, cy1]) <= distance + is_same_label = box0["label_id"] == box1["label_id"] + + return is_good_distance and is_same_label + + def _match_crops(self, crop0, crop1): + embedding0 = self.model.infer(crop0) + embedding1 = self.model.infer(crop1) + + embedding0 = embedding0.reshape(embedding0.size) + embedding1 = embedding1.reshape(embedding1.size) + + return cosine(embedding0, embedding1) + + def _compute_similarity_matrix(self, image0, boxes0, image1, boxes1, + distance): + def _int(number, upper): + return math.floor(numpy.clip(number, 0, upper - 1)) + + DISTANCE_INF = 1000.0 + + matrix = numpy.full([len(boxes0), len(boxes1)], DISTANCE_INF, dtype=float) + for row, box0 in enumerate(boxes0): + w0, h0 = image0.size + xtl0, xbr0, ytl0, ybr0 = ( + _int(box0["points"][0], w0), _int(box0["points"][2], w0), + _int(box0["points"][1], h0), _int(box0["points"][3], h0) + ) + + for col, box1 in enumerate(boxes1): + w1, h1 = image1.size + xtl1, xbr1, ytl1, ybr1 = ( + _int(box1["points"][0], w1), _int(box1["points"][2], w1), + _int(box1["points"][1], h1), _int(box1["points"][3], h1) + ) + + if not self._match_boxes(box0, box1, distance): + continue + + crop0 = image0.crop((xtl0, ytl0, xbr0, ybr0)) + crop1 = image1.crop((xtl1, ytl1, xbr1, ybr1)) + matrix[row][col] = self._match_crops(crop0, crop1) + + return matrix diff --git a/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/function.yaml b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/function.yaml new file mode 100644 index 00000000..03a1f3c1 --- /dev/null +++ b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/function.yaml @@ -0,0 +1,76 @@ +metadata: + name: openvino.omz.semantic-segmentation-adas-0001 + namespace: cvat + annotations: + name: Semantic segmentation for ADAS + type: detector + framework: openvino + spec: | + [ + { "id": 0, "name": "road" }, + { "id": 1, "name": "sidewalk" }, + { "id": 2, "name": "building" }, + { "id": 3, "name": "wall" }, + { "id": 4, "name": "fence" }, + { "id": 5, "name": "pole" }, + { "id": 6, "name": "traffic light" }, + { "id": 7, "name": "traffic sign" }, + { "id": 8, "name": "vegetation" }, + { "id": 9, "name": "terrain" }, + { "id": 10, "name": "sky" }, + { "id": 11, "name": "person" }, + { "id": 12, "name": "rider" }, + { "id": 13, "name": "car" }, + { "id": 14, "name": "truck" }, + { "id": 15, "name": "bus" }, + { "id": 16, "name": "train" }, + { "id": 17, "name": "motorcycle" }, + { "id": 18, "name": "bicycle" }, + { "id": 19, "name": "ego-vehicle" }, + { "id": 20, "name": "background" } + ] + +spec: + description: Segmentation network to classify each pixel into typical 20 classes for ADAS + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/common/python3 + + build: + image: cvat/openvino.omz.intel.semantic-segmentation-adas-0001 + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name semantic-segmentation-adas-0001 -o /opt/nuclio/open_model_zoo + + + postCopy: + - kind: RUN + value: apt update && DEBIAN_FRONTEND=noninteractive apt install --no-install-recommends -y python3-skimage + - kind: RUN + value: pip3 install "numpy<1.16.0" # workaround for skimage + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/main.py b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/main.py new file mode 100644 index 00000000..9942bc02 --- /dev/null +++ b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/main.py @@ -0,0 +1,32 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler +import yaml + +def init_context(context): + context.logger.info("Init context... 0%") + + # Read labels + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} + + # Read the DL model + model = ModelHandler(labels) + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run semantic-segmentation-adas-0001 model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + results = context.user_data.model.infer(image, threshold) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/model_handler.py b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/model_handler.py new file mode 100644 index 00000000..5b4bb722 --- /dev/null +++ b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/model_handler.py @@ -0,0 +1,50 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +import cv2 +import numpy as np +from skimage.measure import approximate_polygon, find_contours +from model_loader import ModelLoader + +class ModelHandler: + def __init__(self, labels): + base_dir = os.environ.get("MODEL_PATH", + "/opt/nuclio/open_model_zoo/intel/semantic-segmentation-adas-0001/FP32") + model_xml = os.path.join(base_dir, "semantic-segmentation-adas-0001.xml") + model_bin = os.path.join(base_dir, "semantic-segmentation-adas-0001.bin") + self.model = ModelLoader(model_xml, model_bin) + self.labels = labels + + def infer(self, image, threshold): + output_layer = self.model.infer(image) + + results = [] + mask = output_layer[0, 0, :, :] + width, height = mask.shape + + for i in range(len(self.labels)): + mask_by_label = np.zeros((width, height), dtype=np.uint8) + + mask_by_label = ((mask == float(i)) * 255).astype(np.float32) + mask_by_label = cv2.resize(mask_by_label, + dsize=(image.width, image.height), + interpolation=cv2.INTER_CUBIC) + + contours = find_contours(mask_by_label, 0.8) + + for contour in contours: + contour = np.flip(contour, axis=1) + contour = approximate_polygon(contour, tolerance=2.5) + if len(contour) < 3: + continue + + results.append({ + "confidence": None, + "label": self.labels.get(i, "unknown"), + "points": contour.ravel().tolist(), + "type": "polygon", + }) + + return results \ No newline at end of file diff --git a/serverless/openvino/omz/intel/text-detection-0004/nuclio/function.yaml b/serverless/openvino/omz/intel/text-detection-0004/nuclio/function.yaml new file mode 100644 index 00000000..66317007 --- /dev/null +++ b/serverless/openvino/omz/intel/text-detection-0004/nuclio/function.yaml @@ -0,0 +1,49 @@ +metadata: + name: openvino.omz.intel.text-detection-0004 + namespace: cvat + annotations: + name: Text detection v4 + type: detector + framework: openvino + spec: | + [ + { "id": 1, "name": "text" } + ] + +spec: + description: Text detector based on PixelLink architecture with MobileNetV2-like as a backbone for indoor/outdoor scenes. + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/common/python3 + + build: + image: cvat/openvino.omz.intel.text-detection-0004 + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name text-detection-0004 -o /opt/nuclio/open_model_zoo + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/openvino/omz/intel/text-detection-0004/nuclio/main.py b/serverless/openvino/omz/intel/text-detection-0004/nuclio/main.py new file mode 100644 index 00000000..ab54e76c --- /dev/null +++ b/serverless/openvino/omz/intel/text-detection-0004/nuclio/main.py @@ -0,0 +1,34 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler +import yaml + +def init_context(context): + context.logger.info("Init context... 0%") + + # Read labels + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} + + # Read the DL model + model = ModelHandler(labels) + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run text-detection-0004 model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + pixel_threshold = float(data.get("pixel_threshold", 0.8)) + link_threshold = float(data.get("link_threshold", 0.8)) + image = Image.open(buf) + + results = context.user_data.model.infer(image, + pixel_threshold, link_threshold) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py b/serverless/openvino/omz/intel/text-detection-0004/nuclio/model_handler.py similarity index 83% rename from utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py rename to serverless/openvino/omz/intel/text-detection-0004/nuclio/model_handler.py index b0f105ec..6ea78c1e 100644 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py +++ b/serverless/openvino/omz/intel/text-detection-0004/nuclio/model_handler.py @@ -1,18 +1,21 @@ -# SPDX-License-Identifier: MIT` +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT +import os import cv2 import numpy as np - +from model_loader import ModelLoader class PixelLinkDecoder(): - def __init__(self): + def __init__(self, pixel_threshold, link_threshold): four_neighbours = False if four_neighbours: self._get_neighbours = self._get_neighbours_4 else: self._get_neighbours = self._get_neighbours_8 - self.pixel_conf_threshold = 0.8 - self.link_conf_threshold = 0.8 + self.pixel_conf_threshold = pixel_threshold + self.link_conf_threshold = link_threshold def decode(self, height, width, detections: dict): self.image_height = height @@ -186,12 +189,29 @@ class PixelLinkDecoder(): self._get_all() self._mask_to_bboxes() - -label = 1 -pcd = PixelLinkDecoder() -for detection in detections: - frame = detection['frame_id'] - pcd.decode(detection['frame_height'], detection['frame_width'], detection['detections']) - for box in pcd.bboxes: - box = [[int(b[0]), int(b[1])] for b in box] - results.add_polygon(box, label, frame) +class ModelHandler: + def __init__(self, labels): + base_dir = os.environ.get("MODEL_PATH", + "/opt/nuclio/open_model_zoo/intel/text-detection-0004/FP32") + model_xml = os.path.join(base_dir, "text-detection-0004.xml") + model_bin = os.path.join(base_dir, "text-detection-0004.bin") + self.model = ModelLoader(model_xml, model_bin) + self.labels = labels + + def infer(self, image, pixel_threshold, link_threshold): + output_layer = self.model.infer(image) + + results = [] + obj_class = 1 + pcd = PixelLinkDecoder(pixel_threshold, link_threshold) + + pcd.decode(image.height, image.width, output_layer) + for box in pcd.bboxes: + results.append({ + "confidence": None, + "label": self.labels.get(obj_class, "unknown"), + "points": box.ravel().tolist(), + "type": "polygon", + }) + + return results \ No newline at end of file diff --git a/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml new file mode 100644 index 00000000..9cb8b6bf --- /dev/null +++ b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -0,0 +1,130 @@ +metadata: + name: openvino.omz.public.faster_rcnn_inception_v2_coco + namespace: cvat + annotations: + name: Faster RCNN + type: detector + framework: openvino + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] + +spec: + description: Faster RCNN inception v2 COCO via Intel OpenVINO toolkit + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/common/python3 + + build: + image: cvat/openvino.omz.public.faster_rcnn_inception_v2_coco + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name faster_rcnn_inception_v2_coco -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name faster_rcnn_inception_v2_coco --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/main.py new file mode 100644 index 00000000..6ae5c801 --- /dev/null +++ b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -0,0 +1,32 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler +import yaml + +def init_context(context): + context.logger.info("Init context... 0%") + + # Read labels + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} + + # Read the DL model + model = ModelHandler(labels) + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run faster_rcnn_inception_v2_coco model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + results = context.user_data.model.infer(image, threshold) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/model_handler.py b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/model_handler.py new file mode 100644 index 00000000..60838779 --- /dev/null +++ b/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio/model_handler.py @@ -0,0 +1,39 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +from model_loader import ModelLoader + +class ModelHandler: + def __init__(self, labels): + base_dir = os.environ.get("MODEL_PATH", + "/opt/nuclio/open_model_zoo/public/faster_rcnn_inception_v2_coco/FP32") + model_xml = os.path.join(base_dir, "faster_rcnn_inception_v2_coco.xml") + model_bin = os.path.join(base_dir, "faster_rcnn_inception_v2_coco.bin") + self.model = ModelLoader(model_xml, model_bin) + self.labels = labels + + def infer(self, image, threshold): + output_layer = self.model.infer(image) + + results = [] + prediction = output_layer[0][0] + for obj in prediction: + obj_class = int(obj[1]) + obj_value = obj[2] + obj_label = self.labels.get(obj_class, "unknown") + if obj_value >= threshold: + xtl = obj[3] * image.width + ytl = obj[4] * image.height + xbr = obj[5] * image.width + ybr = obj[6] * image.height + + results.append({ + "confidence": str(obj_value), + "label": obj_label, + "points": [xtl, ytl, xbr, ybr], + "type": "rectangle", + }) + + return results \ No newline at end of file diff --git a/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml new file mode 100644 index 00000000..f08ef796 --- /dev/null +++ b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -0,0 +1,139 @@ +# To build the function you need to adjust docker settings. Be sure that you +# have enough memory (more than 4GB). Look here how to do that +# https://stackoverflow.com/questions/44417159/docker-process-killed-with-cryptic-killed-message +metadata: + name: openvino.omz.public.mask_rcnn_inception_resnet_v2_atrous_coco + namespace: cvat + annotations: + name: Mask RCNN + type: detector + framework: openvino + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] + +spec: + description: Mask RCNN inception resnet v2 COCO via Intel OpenVINO + runtime: "python:3.6" + handler: main:handler + eventTimeout: 60s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/common/python3 + + build: + image: cvat/openvino.omz.public.mask_rcnn_inception_resnet_v2_atrous_coco + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name mask_rcnn_inception_resnet_v2_atrous_coco -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name mask_rcnn_inception_resnet_v2_atrous_coco --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo + + postCopy: + - kind: RUN + value: apt update && DEBIAN_FRONTEND=noninteractive apt install --no-install-recommends -y python3-skimage + - kind: RUN + value: pip3 install "numpy<1.16.0" # workaround for skimage + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py new file mode 100644 index 00000000..8fc7d285 --- /dev/null +++ b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py @@ -0,0 +1,32 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler +import yaml + +def init_context(context): + context.logger.info("Init context... 0%") + + # Read labels + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} + + # Read the DL model + model = ModelHandler(labels) + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run mask_rcnn_inception_resnet_v2_atrous_coco model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.2)) + image = Image.open(buf) + + results = context.user_data.model.infer(image, threshold) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_handler.py b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_handler.py new file mode 100644 index 00000000..a508aaa4 --- /dev/null +++ b/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_handler.py @@ -0,0 +1,77 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +import cv2 +import numpy as np +from model_loader import ModelLoader +from skimage.measure import approximate_polygon, find_contours + + +MASK_THRESHOLD = 0.5 + +# Ref: https://software.intel.com/en-us/forums/computer-vision/topic/804895 +def segm_postprocess(box: list, raw_cls_mask, im_h, im_w): + ymin, xmin, ymax, xmax = box + + width = int(abs(xmax - xmin)) + height = int(abs(ymax - ymin)) + + result = np.zeros((im_h, im_w), dtype=np.uint8) + resized_mask = cv2.resize(raw_cls_mask, dsize=(height, width), interpolation=cv2.INTER_CUBIC) + + # extract the ROI of the image + ymin = int(round(ymin)) + xmin = int(round(xmin)) + ymax = ymin + height + xmax = xmin + width + result[xmin:xmax, ymin:ymax] = (resized_mask>MASK_THRESHOLD).astype(np.uint8) * 255 + + return result + +class ModelHandler: + def __init__(self, labels): + base_dir = os.environ.get("MODEL_PATH", + "/opt/nuclio/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/FP32") + model_xml = os.path.join(base_dir, "mask_rcnn_inception_resnet_v2_atrous_coco.xml") + model_bin = os.path.join(base_dir, "mask_rcnn_inception_resnet_v2_atrous_coco.bin") + self.model = ModelLoader(model_xml, model_bin) + self.labels = labels + + def infer(self, image, threshold): + output_layer = self.model.infer(image) + + results = [] + masks = output_layer['masks'] + boxes = output_layer['reshape_do_2d'] + + for index, box in enumerate(boxes): + obj_class = int(box[1]) + obj_value = box[2] + obj_label = self.labels.get(obj_class, "unknown") + if obj_value >= threshold: + xtl = box[3] * image.width + ytl = box[4] * image.height + xbr = box[5] * image.width + ybr = box[6] * image.height + mask = masks[index][obj_class - 1] + + mask = segm_postprocess((xtl, ytl, xbr, ybr), + mask, image.height, image.width) + + contours = find_contours(mask, MASK_THRESHOLD) + contour = contours[0] + contour = np.flip(contour, axis=1) + contour = approximate_polygon(contour, tolerance=2.5) + if len(contour) < 3: + continue + + results.append({ + "confidence": str(obj_value), + "label": obj_label, + "points": contour.ravel().tolist(), + "type": "polygon", + }) + + return results \ No newline at end of file diff --git a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/function.yaml b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/function.yaml new file mode 100644 index 00000000..31ea02b4 --- /dev/null +++ b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/function.yaml @@ -0,0 +1,131 @@ +metadata: + name: openvino.omz.public.yolo-v3-tf + namespace: cvat + annotations: + name: YOLO v3 + type: detector + framework: openvino + spec: | + [ + { "id": 0, "name": "person" }, + { "id": 1, "name": "bicycle" }, + { "id": 2, "name": "car" }, + { "id": 3, "name": "motorbike" }, + { "id": 4, "name": "aeroplane" }, + { "id": 5, "name": "bus" }, + { "id": 6, "name": "train" }, + { "id": 7, "name": "truck" }, + { "id": 8, "name": "boat" }, + { "id": 9, "name": "traffic light" }, + { "id": 10, "name": "fire hydrant" }, + { "id": 11, "name": "stop sign" }, + { "id": 12, "name": "parking meter" }, + { "id": 13, "name": "bench" }, + { "id": 14, "name": "bird" }, + { "id": 15, "name": "cat" }, + { "id": 16, "name": "dog" }, + { "id": 17, "name": "horse" }, + { "id": 18, "name": "sheep" }, + { "id": 19, "name": "cow" }, + { "id": 20, "name": "elephant" }, + { "id": 21, "name": "bear" }, + { "id": 22, "name": "zebra" }, + { "id": 23, "name": "giraffe" }, + { "id": 24, "name": "backpack" }, + { "id": 25, "name": "umbrella" }, + { "id": 26, "name": "handbag" }, + { "id": 27, "name": "tie" }, + { "id": 28, "name": "suitcase" }, + { "id": 29, "name": "frisbee" }, + { "id": 30, "name": "skis" }, + { "id": 31, "name": "snowboard" }, + { "id": 32, "name": "sports ball" }, + { "id": 33, "name": "kite" }, + { "id": 34, "name": "baseball bat" }, + { "id": 35, "name": "baseball glove" }, + { "id": 36, "name": "skateboard" }, + { "id": 37, "name": "surfboard" }, + { "id": 38, "name": "tennis racket" }, + { "id": 39, "name": "bottle" }, + { "id": 40, "name": "wine glass" }, + { "id": 41, "name": "cup" }, + { "id": 42, "name": "fork" }, + { "id": 43, "name": "knife" }, + { "id": 44, "name": "spoon" }, + { "id": 45, "name": "bowl" }, + { "id": 46, "name": "banana" }, + { "id": 47, "name": "apple" }, + { "id": 48, "name": "sandwich" }, + { "id": 49, "name": "orange" }, + { "id": 50, "name": "broccoli" }, + { "id": 51, "name": "carrot" }, + { "id": 52, "name": "hot dog" }, + { "id": 53, "name": "pizza" }, + { "id": 54, "name": "donut" }, + { "id": 55, "name": "cake" }, + { "id": 56, "name": "chair" }, + { "id": 57, "name": "sofa" }, + { "id": 58, "name": "pottedplant" }, + { "id": 59, "name": "bed" }, + { "id": 60, "name": "diningtable" }, + { "id": 61, "name": "toilet" }, + { "id": 62, "name": "tvmonitor" }, + { "id": 63, "name": "laptop" }, + { "id": 64, "name": "mouse" }, + { "id": 65, "name": "remote" }, + { "id": 66, "name": "keyboard" }, + { "id": 67, "name": "cell phone" }, + { "id": 68, "name": "microwave" }, + { "id": 69, "name": "oven" }, + { "id": 70, "name": "toaster" }, + { "id": 71, "name": "sink" }, + { "id": 72, "name": "refrigerator" }, + { "id": 73, "name": "book" }, + { "id": 74, "name": "clock" }, + { "id": 75, "name": "vase" }, + { "id": 76, "name": "scissors" }, + { "id": 77, "name": "teddy bear" }, + { "id": 78, "name": "hair drier" }, + { "id": 79, "name": "toothbrush" } + ] + +spec: + description: YOLO v3 via Intel OpenVINO + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/common/python3 + + build: + image: cvat/openvino.omz.public.yolo-v3-tf + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name yolo-v3-tf -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name yolo-v3-tf --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo + + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/main.py b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/main.py new file mode 100644 index 00000000..806ab654 --- /dev/null +++ b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/main.py @@ -0,0 +1,32 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler +import yaml + +def init_context(context): + context.logger.info("Init context... 0%") + + # Read labels + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} + + # Read the DL model + model = ModelHandler(labels) + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run yolo-v3-tf model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + results = context.user_data.model.infer(image, threshold) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/model_handler.py b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/model_handler.py new file mode 100644 index 00000000..47fa5842 --- /dev/null +++ b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/model_handler.py @@ -0,0 +1,160 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +from math import exp +from model_loader import ModelLoader + +class YoloParams: + # ------------------------------------------- Extracting layer parameters ------------------------------------------ + # Magic numbers are copied from yolo samples + def __init__(self, param, side): + self.num = 3 if 'num' not in param else int(param['num']) + self.coords = 4 if 'coords' not in param else int(param['coords']) + self.classes = 80 if 'classes' not in param else int(param['classes']) + self.side = side + self.anchors = [10.0, 13.0, 16.0, 30.0, 33.0, 23.0, 30.0, 61.0, 62.0, 45.0, 59.0, 119.0, 116.0, 90.0, 156.0, + 198.0, + 373.0, 326.0] if 'anchors' not in param else [float(a) for a in param['anchors'].split(',')] + + self.isYoloV3 = False + + if param.get('mask'): + mask = [int(idx) for idx in param['mask'].split(',')] + self.num = len(mask) + + maskedAnchors = [] + for idx in mask: + maskedAnchors += [self.anchors[idx * 2], self.anchors[idx * 2 + 1]] + self.anchors = maskedAnchors + + self.isYoloV3 = True # Weak way to determine but the only one. + + +def entry_index(side, coord, classes, location, entry): + side_power_2 = side ** 2 + n = location // side_power_2 + loc = location % side_power_2 + return int(side_power_2 * (n * (coord + classes + 1) + entry) + loc) + + +def scale_bbox(x, y, h, w, class_id, confidence, h_scale, w_scale): + xmin = int((x - w / 2) * w_scale) + ymin = int((y - h / 2) * h_scale) + xmax = int(xmin + w * w_scale) + ymax = int(ymin + h * h_scale) + return dict(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, class_id=class_id, confidence=confidence) + + +def parse_yolo_region(blob, resized_image_shape, original_im_shape, params, threshold): + # ------------------------------------------ Validating output parameters ------------------------------------------ + _, _, out_blob_h, out_blob_w = blob.shape + assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ + "be equal to width. Current height = {}, current width = {}" \ + "".format(out_blob_h, out_blob_w) + + # ------------------------------------------ Extracting layer parameters ------------------------------------------- + orig_im_h, orig_im_w = original_im_shape + resized_image_h, resized_image_w = resized_image_shape + objects = list() + predictions = blob.flatten() + side_square = params.side * params.side + + # ------------------------------------------- Parsing YOLO Region output ------------------------------------------- + for i in range(side_square): + row = i // params.side + col = i % params.side + for n in range(params.num): + obj_index = entry_index(params.side, params.coords, params.classes, n * side_square + i, params.coords) + scale = predictions[obj_index] + if scale < threshold: + continue + box_index = entry_index(params.side, params.coords, params.classes, n * side_square + i, 0) + # Network produces location predictions in absolute coordinates of feature maps. + # Scale it to relative coordinates. + x = (col + predictions[box_index + 0 * side_square]) / params.side + y = (row + predictions[box_index + 1 * side_square]) / params.side + # Value for exp is very big number in some cases so following construction is using here + try: + w_exp = exp(predictions[box_index + 2 * side_square]) + h_exp = exp(predictions[box_index + 3 * side_square]) + except OverflowError: + continue + # Depends on topology we need to normalize sizes by feature maps (up to YOLOv3) or by input shape (YOLOv3) + w = w_exp * params.anchors[2 * n] / (resized_image_w if params.isYoloV3 else params.side) + h = h_exp * params.anchors[2 * n + 1] / (resized_image_h if params.isYoloV3 else params.side) + for j in range(params.classes): + class_index = entry_index(params.side, params.coords, params.classes, n * side_square + i, + params.coords + 1 + j) + confidence = scale * predictions[class_index] + if confidence < threshold: + continue + objects.append(scale_bbox(x=x, y=y, h=h, w=w, class_id=j, confidence=confidence, + h_scale=orig_im_h, w_scale=orig_im_w)) + return objects + + +def intersection_over_union(box_1, box_2): + width_of_overlap_area = min(box_1['xmax'], box_2['xmax']) - max(box_1['xmin'], box_2['xmin']) + height_of_overlap_area = min(box_1['ymax'], box_2['ymax']) - max(box_1['ymin'], box_2['ymin']) + if width_of_overlap_area < 0 or height_of_overlap_area < 0: + area_of_overlap = 0 + else: + area_of_overlap = width_of_overlap_area * height_of_overlap_area + box_1_area = (box_1['ymax'] - box_1['ymin']) * (box_1['xmax'] - box_1['xmin']) + box_2_area = (box_2['ymax'] - box_2['ymin']) * (box_2['xmax'] - box_2['xmin']) + area_of_union = box_1_area + box_2_area - area_of_overlap + if area_of_union == 0: + return 0 + return area_of_overlap / area_of_union + + +class ModelHandler: + def __init__(self, labels): + base_dir = os.environ.get("MODEL_PATH", + "/opt/nuclio/open_model_zoo/public/yolo-v3-tf/FP32") + model_xml = os.path.join(base_dir, "yolo-v3-tf.xml") + model_bin = os.path.join(base_dir, "yolo-v3-tf.bin") + self.model = ModelLoader(model_xml, model_bin) + self.labels = labels + + def infer(self, image, threshold): + output_layer = self.model.infer(image) + + # Collecting object detection results + objects = [] + origin_im_size = (image.height, image.width) + for layer_name, out_blob in output_layer.items(): + out_blob = out_blob.reshape(self.model.layers[self.model.layers[layer_name].parents[0]].shape) + layer_params = YoloParams(self.model.layers[layer_name].params, out_blob.shape[2]) + objects += parse_yolo_region(out_blob, self.model.input_size(), + origin_im_size, layer_params, threshold) + + # Filtering overlapping boxes (non-maximum supression) + IOU_THRESHOLD = 0.4 + objects = sorted(objects, key=lambda obj : obj['confidence'], reverse=True) + for i, obj in enumerate(objects): + if obj['confidence'] == 0: + continue + for j in range(i + 1, len(objects)): + if intersection_over_union(obj, objects[j]) > IOU_THRESHOLD: + objects[j]['confidence'] = 0 + + results = [] + for obj in objects: + if obj['confidence'] >= threshold: + xtl = max(obj['xmin'], 0) + ytl = max(obj['ymin'], 0) + xbr = min(obj['xmax'], image.width) + ybr = min(obj['ymax'], image.height) + obj_class = int(obj['class_id']) + + results.append({ + "confidence": str(obj['confidence']), + "label": self.labels.get(obj_class, "unknown"), + "points": [xtl, ytl, xbr, ybr], + "type": "rectangle", + }) + + return results \ No newline at end of file diff --git a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml new file mode 100644 index 00000000..bb165c79 --- /dev/null +++ b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml @@ -0,0 +1,56 @@ +metadata: + name: pth.foolwood.siammask + namespace: cvat + annotations: + name: SiamMask + type: tracker + spec: + framework: pytorch + +spec: + description: Fast Online Object Tracking and Segmentation + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: PYTHONPATH + value: /opt/nuclio/SiamMask:/opt/nuclio/SiamMask/experiments/siammask_sharp + + build: + image: cvat/pth.foolwood.siammask + baseImage: continuumio/miniconda3 + + directives: + preCopy: + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: conda create -y -n siammask python=3.6 + - kind: SHELL + value: '["conda", "run", "-n", "siammask", "/bin/bash", "-c"]' + - kind: RUN + value: git clone https://github.com/foolwood/SiamMask.git + - kind: RUN + value: pip install -r SiamMask/requirements.txt jsonpickle + - kind: RUN + value: conda install -y gcc_linux-64 + - kind: RUN + value: cd SiamMask && bash make.sh && cd - + - kind: RUN + value: wget -P SiamMask/experiments/siammask_sharp http://www.robots.ox.ac.uk/~qwang/SiamMask_DAVIS.pth + - kind: ENTRYPOINT + value: '["conda", "run", "-n", "siammask"]' + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/pytorch/foolwood/siammask/nuclio/main.py b/serverless/pytorch/foolwood/siammask/nuclio/main.py new file mode 100644 index 00000000..51c3669a --- /dev/null +++ b/serverless/pytorch/foolwood/siammask/nuclio/main.py @@ -0,0 +1,27 @@ +import json +import base64 +from PIL import Image +import io +from model_handler import ModelHandler + +def init_context(context): + context.logger.info("Init context... 0%") + + # Read the DL model + model = ModelHandler() + setattr(context.user_data, 'model', model) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run SiamMask model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + shape = data.get("shape") + state = data.get("state") + image = Image.open(buf) + + results = context.user_data.model.infer(image, shape, state) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/pytorch/foolwood/siammask/nuclio/model_handler.py b/serverless/pytorch/foolwood/siammask/nuclio/model_handler.py new file mode 100644 index 00000000..826c7922 --- /dev/null +++ b/serverless/pytorch/foolwood/siammask/nuclio/model_handler.py @@ -0,0 +1,66 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from tools.test import * +import os +from copy import copy +import jsonpickle +import numpy as np + +class ModelHandler: + def __init__(self): + # Setup device + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + torch.backends.cudnn.benchmark = True + + base_dir = os.environ.get("MODEL_PATH", + "/opt/nuclio/SiamMask/experiments/siammask_sharp") + class configPath: + config = os.path.join(base_dir, "config_davis.json") + + self.config = load_config(configPath) + from custom import Custom + siammask = Custom(anchors=self.config['anchors']) + self.siammask = load_pretrain(siammask, os.path.join(base_dir, "SiamMask_DAVIS.pth")) + self.siammask.eval().to(self.device) + + def encode_state(self, state): + state['net.zf'] = state['net'].zf + state.pop('net', None) + state.pop('mask', None) + + for k,v in state.items(): + state[k] = jsonpickle.encode(v) + + return state + + def decode_state(self, state): + for k,v in state.items(): + state[k] = jsonpickle.decode(v) + + state['net'] = copy(self.siammask) + state['net'].zf = state['net.zf'] + del state['net.zf'] + + return state + + def infer(self, image, shape, state): + image = np.array(image) + if state is None: # init tracking + xtl, ytl, xbr, ybr = shape + target_pos = np.array([(xtl + xbr) / 2, (ytl + ybr) / 2]) + target_sz = np.array([xbr - xtl, ybr - ytl]) + siammask = copy(self.siammask) # don't modify self.siammask + state = siamese_init(image, target_pos, target_sz, siammask, + self.config['hp'], device=self.device) + state = self.encode_state(state) + else: # track + state = self.decode_state(state) + state = siamese_track(state, image, mask_enable=True, + refine_enable=True, device=self.device) + shape = state['ploygon'].flatten().tolist() + state = self.encode_state(state) + + return {"shape": shape, "state": state} + diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml new file mode 100644 index 00000000..c58617ce --- /dev/null +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -0,0 +1,132 @@ +metadata: + name: tf.faster_rcnn_inception_v2_coco + namespace: cvat + annotations: + name: Faster RCNN via Tensorflow + type: detector + framework: tensorflow + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] + +spec: + description: Faster RCNN from Tensorflow Object Detection API + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + + build: + image: cvat/tf.faster_rcnn_inception_v2_coco + baseImage: tensorflow/tensorflow:2.1.1 + + directives: + preCopy: + - kind: RUN + value: apt install curl + - kind: WORKDIR + value: /opt/nuclio + + postCopy: + - kind: RUN + value: curl -O http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz + - kind: RUN + value: tar -xzf faster_rcnn_inception_v2_coco_2018_01_28.tar.gz && rm faster_rcnn_inception_v2_coco_2018_01_28.tar.gz + - kind: RUN + value: ln -s faster_rcnn_inception_v2_coco_2018_01_28 faster_rcnn + - kind: RUN + value: pip install pillow pyyaml + + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/main.py new file mode 100644 index 00000000..8bcad27c --- /dev/null +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -0,0 +1,48 @@ +import json +import base64 +import io +from PIL import Image +import yaml +from model_loader import ModelLoader + + +def init_context(context): + context.logger.info("Init context... 0%") + model_path = "/opt/nuclio/faster_rcnn/frozen_inference_graph.pb" + model_handler = ModelLoader(model_path) + setattr(context.user_data, 'model_handler', model_handler) + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} + setattr(context.user_data, "labels", labels) + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run faster_rcnn_inception_v2_coco model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + (boxes, scores, classes, num_detections) = context.user_data.model_handler.infer(image) + + results = [] + for i in range(int(num_detections[0])): + obj_class = int(classes[0][i]) + obj_score = scores[0][i] + obj_label = context.user_data.labels.get(obj_class, "unknown") + if obj_score >= threshold: + xtl = boxes[0][i][1] * image.width + ytl = boxes[0][i][0] * image.height + xbr = boxes[0][i][3] * image.width + ybr = boxes[0][i][2] * image.height + + results.append({ + "confidence": str(obj_score), + "label": obj_label, + "points": [xtl, ytl, xbr, ybr], + "type": "rectangle", + }) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/model_loader.py b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/model_loader.py new file mode 100644 index 00000000..8158eee3 --- /dev/null +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/model_loader.py @@ -0,0 +1,43 @@ + +import numpy as np +from PIL import Image +import tensorflow.compat.v1 as tf +tf.disable_v2_behavior() + +class ModelLoader: + def __init__(self, model_path): + self.session = None + + detection_graph = tf.Graph() + with detection_graph.as_default(): + od_graph_def = tf.GraphDef() + with tf.gfile.GFile(model_path, 'rb') as fid: + serialized_graph = fid.read() + od_graph_def.ParseFromString(serialized_graph) + tf.import_graph_def(od_graph_def, name='') + + config = tf.ConfigProto() + config.gpu_options.allow_growth = True + self.session = tf.Session(graph=detection_graph, config=config) + + self.image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') + self.boxes = detection_graph.get_tensor_by_name('detection_boxes:0') + self.scores = detection_graph.get_tensor_by_name('detection_scores:0') + self.classes = detection_graph.get_tensor_by_name('detection_classes:0') + self.num_detections = detection_graph.get_tensor_by_name('num_detections:0') + + def __del__(self): + if self.session: + self.session.close() + del self.session + + def infer(self, image): + width, height = image.size + if width > 1920 or height > 1080: + image = image.resize((width // 2, height // 2), Image.ANTIALIAS) + image_np = np.array(image.getdata()).reshape((image.height, image.width, 3)).astype(np.uint8) + image_np = np.expand_dims(image_np, axis=0) + + return self.session.run( + [self.boxes, self.scores, self.classes, self.num_detections], + feed_dict={self.image_tensor: image_np}) diff --git a/serverless/tensorflow/matterport/mask_rcnn/nuclio/function.yaml b/serverless/tensorflow/matterport/mask_rcnn/nuclio/function.yaml new file mode 100644 index 00000000..b50868a6 --- /dev/null +++ b/serverless/tensorflow/matterport/mask_rcnn/nuclio/function.yaml @@ -0,0 +1,133 @@ +metadata: + name: tf.matterport.mask_rcnn + namespace: cvat + annotations: + name: Mask RCNN via Tensorflow + type: detector + framework: tensorflow + spec: | + [ + { "id": 0, "name": "BG" }, + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id": 10, "name": "traffic_light" }, + { "id": 11, "name": "fire_hydrant" }, + { "id": 12, "name": "stop_sign" }, + { "id": 13, "name": "parking_meter" }, + { "id": 14, "name": "bench" }, + { "id": 15, "name": "bird" }, + { "id": 16, "name": "cat" }, + { "id": 17, "name": "dog" }, + { "id": 18, "name": "horse" }, + { "id": 19, "name": "sheep" }, + { "id": 20, "name": "cow" }, + { "id": 21, "name": "elephant" }, + { "id": 22, "name": "bear" }, + { "id": 23, "name": "zebra" }, + { "id": 24, "name": "giraffe" }, + { "id": 25, "name": "backpack" }, + { "id": 26, "name": "umbrella" }, + { "id": 27, "name": "handbag" }, + { "id": 28, "name": "tie" }, + { "id": 29, "name": "suitcase" }, + { "id": 30, "name": "frisbee" }, + { "id": 31, "name": "skis" }, + { "id": 32, "name": "snowboard" }, + { "id": 33, "name": "sports_ball" }, + { "id": 34, "name": "kite" }, + { "id": 35, "name": "baseball_bat" }, + { "id": 36, "name": "baseball_glove" }, + { "id": 37, "name": "skateboard" }, + { "id": 38, "name": "surfboard" }, + { "id": 39, "name": "tennis_racket" }, + { "id": 40, "name": "bottle" }, + { "id": 41, "name": "wine_glass" }, + { "id": 42, "name": "cup" }, + { "id": 43, "name": "fork" }, + { "id": 44, "name": "knife" }, + { "id": 45, "name": "spoon" }, + { "id": 46, "name": "bowl" }, + { "id": 47, "name": "banana" }, + { "id": 48, "name": "apple" }, + { "id": 49, "name": "sandwich" }, + { "id": 50, "name": "orange" }, + { "id": 51, "name": "broccoli" }, + { "id": 52, "name": "carrot" }, + { "id": 53, "name": "hot_dog" }, + { "id": 54, "name": "pizza" }, + { "id": 55, "name": "donut" }, + { "id": 56, "name": "cake" }, + { "id": 57, "name": "chair" }, + { "id": 58, "name": "couch" }, + { "id": 59, "name": "potted_plant" }, + { "id": 60, "name": "bed" }, + { "id": 61, "name": "dining_table" }, + { "id": 62, "name": "toilet" }, + { "id": 63, "name": "tv" }, + { "id": 64, "name": "laptop" }, + { "id": 65, "name": "mouse" }, + { "id": 66, "name": "remote" }, + { "id": 67, "name": "keyboard" }, + { "id": 68, "name": "cell_phone" }, + { "id": 69, "name": "microwave" }, + { "id": 70, "name": "oven" }, + { "id": 71, "name": "toaster" }, + { "id": 72, "name": "sink" }, + { "id": 73, "name": "refrigerator" }, + { "id": 74, "name": "book" }, + { "id": 75, "name": "clock" }, + { "id": 76, "name": "vase" }, + { "id": 77, "name": "scissors" }, + { "id": 78, "name": "teddy_bear" }, + { "id": 79, "name": "hair_drier" }, + { "id": 80, "name": "toothbrush" } + ] + +spec: + description: | + An implementation of Mask RCNN on Python 3, Keras, and TensorFlow. + + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: MASK_RCNN_DIR + value: /opt/nuclio/Mask_RCNN + build: + image: cvat/tf.matterport.mask_rcnn + baseImage: tensorflow/tensorflow:2.1.0-py3 + directives: + postCopy: + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: apt update && apt install --no-install-recommends -y git curl libsm6 libxext6 libxrender-dev + - kind: RUN + value: git clone https://github.com/matterport/Mask_RCNN.git + - kind: RUN + value: curl -L https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5 -o Mask_RCNN/mask_rcnn_coco.h5 + - kind: RUN + value: pip3 install -r Mask_RCNN/requirements.txt + - kind: RUN + value: pip3 install pycocotools tensorflow==1.13.1 keras==2.1.0 pillow pyyaml + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/tensorflow/matterport/mask_rcnn/nuclio/main.py b/serverless/tensorflow/matterport/mask_rcnn/nuclio/main.py new file mode 100644 index 00000000..95816dd4 --- /dev/null +++ b/serverless/tensorflow/matterport/mask_rcnn/nuclio/main.py @@ -0,0 +1,32 @@ +import json +import base64 +from PIL import Image +import io +from model_loader import ModelLoader +import numpy as np +import yaml + + +def init_context(context): + context.logger.info("Init context... 0%") + + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} + + model_handler = ModelLoader(labels) + setattr(context.user_data, 'model_handler', model_handler) + + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run tf.matterport.mask_rcnn model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.2)) + image = Image.open(buf) + + results = context.user_data.model_handler.infer(np.array(image), threshold) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) \ No newline at end of file diff --git a/serverless/tensorflow/matterport/mask_rcnn/nuclio/model_loader.py b/serverless/tensorflow/matterport/mask_rcnn/nuclio/model_loader.py new file mode 100644 index 00000000..b210338a --- /dev/null +++ b/serverless/tensorflow/matterport/mask_rcnn/nuclio/model_loader.py @@ -0,0 +1,79 @@ +# Copyright (C) 2018-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +import numpy as np +import sys +from skimage.measure import find_contours, approximate_polygon + +# workaround for tf.placeholder() is not compatible with eager execution +# https://github.com/tensorflow/tensorflow/issues/18165 +import tensorflow as tf +tf.compat.v1.disable_eager_execution() +#import tensorflow.compat.v1 as tf +# tf.disable_v2_behavior() + +# The directory should contain a clone of +# https://github.com/matterport/Mask_RCNN repository and +# downloaded mask_rcnn_coco.h5 model. +MASK_RCNN_DIR = os.environ.get('MASK_RCNN_DIR') +if MASK_RCNN_DIR: + sys.path.append(MASK_RCNN_DIR) # To find local version of the library + sys.path.append(os.path.join(MASK_RCNN_DIR, 'samples/coco')) + +from mrcnn import model as modellib +import coco + +class ModelLoader: + def __init__(self, labels): + COCO_MODEL_PATH = os.path.join(MASK_RCNN_DIR, "mask_rcnn_coco.h5") + if COCO_MODEL_PATH is None: + raise OSError('Model path env not found in the system.') + + class InferenceConfig(coco.CocoConfig): + # Set batch size to 1 since we'll be running inference on + # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU + GPU_COUNT = 1 + IMAGES_PER_GPU = 1 + + # Print config details + self.config = InferenceConfig() + self.config.display() + + self.model = modellib.MaskRCNN(mode="inference", + config=self.config, model_dir=MASK_RCNN_DIR) + self.model.load_weights(COCO_MODEL_PATH, by_name=True) + self.labels = labels + + def infer(self, image, threshold): + output = self.model.detect([image], verbose=1)[0] + + results = [] + MASK_THRESHOLD = 0.5 + for i in range(len(output["rois"])): + score = output["scores"][i] + class_id = output["class_ids"][i] + mask = output["masks"][:,:,i] + if score >= threshold: + mask = mask.astype(np.uint8) + contours = find_contours(mask, MASK_THRESHOLD) + # only one contour exist in our case + contour = contours[0] + contour = np.flip(contour, axis=1) + # Approximate the contour and reduce the number of points + contour = approximate_polygon(contour, tolerance=2.5) + if len(contour) < 3: + continue + label = self.labels[class_id] + + results.append({ + "confidence": str(score), + "label": label, + "points": contour.ravel().tolist(), + "type": "polygon", + }) + + return results + + diff --git a/supervisord.conf b/supervisord.conf index 706f3ec8..a991b2b9 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -23,37 +23,42 @@ priority=1 autorestart=true [program:rqworker_default] -command=%(ENV_HOME)s/wait-for-it.sh redis:6379 -t 0 -- bash -ic \ +command=%(ENV_HOME)s/wait-for-it.sh %(ENV_CVAT_REDIS_HOST)s:6379 -t 0 -- bash -ic \ "exec /usr/bin/python3 %(ENV_HOME)s/manage.py rqworker -v 3 default" environment=SSH_AUTH_SOCK="/tmp/ssh-agent.sock" numprocs=2 process_name=rqworker_default_%(process_num)s [program:rqworker_low] -command=%(ENV_HOME)s/wait-for-it.sh redis:6379 -t 0 -- bash -ic \ +command=%(ENV_HOME)s/wait-for-it.sh %(ENV_CVAT_REDIS_HOST)s:6379 -t 0 -- bash -ic \ "exec /usr/bin/python3 %(ENV_HOME)s/manage.py rqworker -v 3 low" environment=SSH_AUTH_SOCK="/tmp/ssh-agent.sock" numprocs=1 [program:git_status_updater] -command=%(ENV_HOME)s/wait-for-it.sh redis:6379 -t 0 -- bash -ic \ +command=%(ENV_HOME)s/wait-for-it.sh %(ENV_CVAT_REDIS_HOST)s:6379 -t 0 -- bash -ic \ "/usr/bin/python3 ~/manage.py update_git_states" environment=SSH_AUTH_SOCK="/tmp/ssh-agent.sock" numprocs=1 [program:rqscheduler] -command=%(ENV_HOME)s/wait-for-it.sh redis:6379 -t 0 -- bash -ic \ +command=%(ENV_HOME)s/wait-for-it.sh %(ENV_CVAT_REDIS_HOST)s:6379 -t 0 -- bash -ic \ "/usr/bin/python3 /usr/local/bin/rqscheduler --host redis -i 30" environment=SSH_AUTH_SOCK="/tmp/ssh-agent.sock" numprocs=1 +[program:clamav_update] +command=bash -c "if [ \"${CLAM_AV}\" = 'yes' ]; then /usr/bin/freshclam -d \ + -l %(ENV_HOME)s/logs/freshclam.log --foreground=true; fi" +numprocs=1 + [program:runserver] ; Here need to run a couple of commands to initialize DB and copy static files. ; We cannot initialize DB on build because the DB should be online. Also some ; apps are dynamically loaded by an environment variable. It can lead to issues ; with docker cache. Thus it is necessary to run collectstatic here for such ; apps. -command=%(ENV_HOME)s/wait-for-it.sh db:5432 -t 0 -- bash -ic \ +command=%(ENV_HOME)s/wait-for-it.sh %(ENV_CVAT_POSTGRES_HOST)s:5432 -t 0 -- bash -ic \ "rm -f /tmp/cvat-server/httpd.pid && /usr/bin/python3 ~/manage.py migrate && \ /usr/bin/python3 ~/manage.py collectstatic --no-input && \ exec /usr/bin/python3 $HOME/manage.py runmodwsgi --log-to-terminal --port 8080 \ diff --git a/tests/cypress.json b/tests/cypress.json new file mode 100644 index 00000000..59316936 --- /dev/null +++ b/tests/cypress.json @@ -0,0 +1,15 @@ +{ + "video": false, + "baseUrl": "http://localhost:8080", + "viewportWidth": 1300, + "viewportHeight": 960, + "defaultCommandTimeout": 10000, + "env": { + "user": "admin", + "password": "12qwaszx" + }, + "testFiles": [ + "auth_page.js", + "issue_*.js" + ] +} diff --git a/tests/cypress/integration/auth_page.js b/tests/cypress/integration/auth_page.js new file mode 100644 index 00000000..1583a326 --- /dev/null +++ b/tests/cypress/integration/auth_page.js @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +describe('Check server availability', () => { + it('Server web interface is available', () => { + cy.visit('/') + }) + + it('"/auth/login" contains in the URL', () => { + cy.url({ timeout: 8000 }).should('include', '/auth/login') + }) + + it('"Sign in" button is exists', () => { + cy.get('[type="submit"]') + }) + + it('Check placeholder "Username"', () => { + cy.get('input').invoke('attr', 'placeholder').should('contain', 'Username') + }) + + it('Check placeholder "Password"', () => { + cy.get('[type="password"]') + }) + + it('Click to "Sign in" button', () => { + cy.get('[type="submit"]').click() + }) +}) diff --git a/tests/cypress/integration/issue_1429_check_new_label.js b/tests/cypress/integration/issue_1429_check_new_label.js new file mode 100644 index 00000000..955103a3 --- /dev/null +++ b/tests/cypress/integration/issue_1429_check_new_label.js @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Check if the new label reflects in the options', () => { + + const issueId = '1429' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const image = `image_${issueId}.png` + const newLabelName = `New ${labelName}` + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'gray' + + before(() => { + cy.visit('auth/login') + cy.login() + cy.imageGenerator('cypress/fixtures', image, width, height, color, posX, posY, labelName) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, image) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Open a task. Open a job', () => { + cy.openTaskJob(taskName) + }) + it('Return to task page using browser button "previous page"', () => { + cy.go('back') + cy.url().should('include', '/tasks').and('not.contain', '/jobs') + }) + it('Add new label', () => { + cy.contains('button', 'Add label').click() + cy.get('[placeholder="Label name"]').type(newLabelName) + cy.contains('button', 'Done').click() + }) + it('Open the job again', () => { + cy.openJob() + }) + it('Create a shape', () => { + cy.createShape(309, 431, 616, 671) + }) + it('Checking for the new label', () => { + cy.get('#cvat-objects-sidebar-state-item-1') + .find('.ant-select-selection') + .click() + cy.get('.ant-select-dropdown-menu-item') + .should('contain', newLabelName) + }) + }) +}) diff --git a/tests/cypress/integration/issue_1439_blocked_object_info.js b/tests/cypress/integration/issue_1439_blocked_object_info.js new file mode 100644 index 00000000..a729f78c --- /dev/null +++ b/tests/cypress/integration/issue_1439_blocked_object_info.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Information about a blocked object disappears if hover the cursor over another object', () => { + + const issueId = '1439' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const image = `image_${issueId}.png` + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'gray' + + before(() => { + cy.visit('auth/login') + cy.login() + cy.imageGenerator('cypress/fixtures', image, width, height, color, posX, posY, labelName) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, image) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Create multiple objects', () => { + cy.createShape(309, 431, 409, 531) + cy.createShape(200, 300, 300, 400) + }) + it('Lock all objects', () => { + cy.get('.cvat-objects-sidebar-states-header') + .find('.anticon-unlock') + .click() + }) + it('Mousemove to 1st object', () => { + cy.get('#cvat_canvas_shape_1').trigger('mousemove') + }) + it('Mousemove to 2nd object', () => { + cy.get('#cvat_canvas_shape_2').trigger('mousemove') + }) + it('Information about 1st object not exist', () => { + cy.get('#cvat_canvas_text_content') + .contains(`${labelName} 1`) + .should('not.exist') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1444_filter_property_shape.js b/tests/cypress/integration/issue_1444_filter_property_shape.js new file mode 100644 index 00000000..6859869d --- /dev/null +++ b/tests/cypress/integration/issue_1444_filter_property_shape.js @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Filter property "shape" work correctly', () => { + + const issueId = '1444' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const image = `image_${issueId}.png` + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'white' + + before(() => { + cy.visit('auth/login') + cy.login() + cy.imageGenerator('cypress/fixtures', image, width, height, color, posX, posY, labelName) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, image) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Create a shape', () => { + cy.createShape(309, 431, 616, 671) + cy.get('#cvat-objects-sidebar-state-item-1') + .should('contain', '1').and('contain', 'RECTANGLE SHAPE') + }) + it('Create a polygon', () => { + cy.createPolygon('Shape', [ + {x: 300, y: 100}, + {x: 400, y: 400}, + {x: 400, y: 250}, + ]) + cy.get('#cvat-objects-sidebar-state-item-2') + .should('contain', '2').and('contain', 'POLYGON SHAPE') + }) + it('Input filter "shape == "polygon""', () => { + cy.get('.cvat-annotations-filters-input') + .type('shape == "polygon"{Enter}') + }) + it('Only polygon is visible', () => { + cy.get('#cvat_canvas_shape_2') + .should('exist') + cy.get('#cvat_canvas_shape_1') + .should('not.exist') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1599_ch_user_registration.js b/tests/cypress/integration/issue_1599_ch_user_registration.js new file mode 100644 index 00000000..8bbe6c11 --- /dev/null +++ b/tests/cypress/integration/issue_1599_ch_user_registration.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Issue 1599 (Chinese alphabet).', () => { + before(() => { + cy.visit('auth/register') + cy.url().should('include', '/auth/register') + }) + + describe('User registration using the Chinese alphabet.', () => { + it('Filling in the placeholder "First name"', () => { + cy.get('[placeholder="First name"]').type('测试者') + .should('not.have.class', 'has-error') + }) + + it('Filling in the placeholder "Last name"', () => { + cy.get('[placeholder="Last name"]').type('测试') + .should('not.have.class', 'has-error') + }) + + it('Filling in the placeholder "Username"', () => { + cy.get('[placeholder="Username"]').type('Testuser_ch') + }) + + it('Filling in the placeholder "Email address"', () => { + cy.get('[placeholder="Email address"]').type('Testuser_ch@local.local') + }) + + it('Filling in the placeholder "Password"', () => { + cy.get('[placeholder="Password"]').type('Qwerty123!') + }) + + it('Filling in the placeholder "Confirm password"', () => { + cy.get('[placeholder="Confirm password"]').type('Qwerty123!') + }) + + it('Click to "Submit" button', () => { + cy.get('[type="submit"]').click() + }) + + it('Successful registration', () => { + cy.url().should('include', '/tasks') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1599_pl_user_registration.js b/tests/cypress/integration/issue_1599_pl_user_registration.js new file mode 100644 index 00000000..4551ac94 --- /dev/null +++ b/tests/cypress/integration/issue_1599_pl_user_registration.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Issue 1599 (Polish alphabet).', () => { + before(() => { + cy.visit('auth/register') + cy.url().should('include', '/auth/register') + }) + + describe('User registration using the Polish alphabet.', () => { + it('Filling in the placeholder "First name"', () => { + cy.get('[placeholder="First name"]').type('Świętobor') + .should('not.have.class', 'has-error') + }) + + it('Filling in the placeholder "Last name"', () => { + cy.get('[placeholder="Last name"]').type('Сzcić') + .should('not.have.class', 'has-error') + }) + + it('Filling in the placeholder "Username"', () => { + cy.get('[placeholder="Username"]').type('Testuser_pl') + }) + + it('Filling in the placeholder "Email address"', () => { + cy.get('[placeholder="Email address"]').type('Testuser_pl@local.local') + }) + + it('Filling in the placeholder "Password"', () => { + cy.get('[placeholder="Password"]').type('Qwerty123!') + }) + + it('Filling in the placeholder "Confirm password"', () => { + cy.get('[placeholder="Confirm password"]').type('Qwerty123!') + }) + + it('Click to "Submit" button', () => { + cy.get('[type="submit"]').click() + }) + + it('Successful registration', () => { + cy.url().should('include', '/tasks') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1750_err_aam_switch_frames.js b/tests/cypress/integration/issue_1750_err_aam_switch_frames.js new file mode 100644 index 00000000..1c106b4a --- /dev/null +++ b/tests/cypress/integration/issue_1750_err_aam_switch_frames.js @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('An error occurs in AAM when switching to 2 frames, if the frames have objects created in shape mode', () => { + + const issueId = '1750' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const images = [`image_${issueId}_1.png`, + `image_${issueId}_2.png`, + `image_${issueId}_3.png`] + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'gray' + const archiveName = `images_issue_${issueId}.zip` + const archivePath = `cypress/fixtures/${archiveName}` + const imagesFolder = `cypress/fixtures/image_issue_${issueId}` + const directoryToArchive = imagesFolder + + before(() => { + cy.visit('auth/login') + cy.login() + for (let img of images) { + cy.imageGenerator(imagesFolder, img, width, height, color, posX, posY, labelName) + } + cy.createZipArchive(directoryToArchive, archivePath) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, archiveName) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Create multiple objects', () => { + cy.createShape(309, 431, 409, 531) + cy.createShape(200, 300, 300, 400) + }) + it('Go to AAM', () => { + cy.get('.cvat-workspace-selector') + .click() + cy.get('.ant-select-dropdown-menu-item') + .contains('Attribute annotation') + .click() + .should('contain.text', 'Attribute annotation') + }) + it('Go to next frame', () => { + cy.get('.cvat-player-next-button') + .click() + cy.get('.cvat-player-frame-selector').within(() => { + cy.get('input[role="spinbutton"]') + .should('have.value', '1') + }) + }) + it('Go to previous frame', () => { + cy.get('.cvat-player-previous-button') + .click() + cy.get('.cvat-player-frame-selector').within(() => { + cy.get('input[role="spinbutton"]') + .should('have.value', '0') + }) + }) + it('Go to next object', () => { + cy.get('.attribute-annotation-sidebar-object-switcher') + .should('contain', `${labelName} 1 [1/2]`) + .find('.anticon-right') + .click({force: true}) + }) + it('Page with the error is missing', () => { + cy.contains('Oops, something went wrong') + .should('not.exist') + cy.get('.attribute-annotation-sidebar-object-switcher') + .should('contain', `${labelName} 2 [2/2]`) + }) + }) +}) diff --git a/tests/cypress/integration/issue_1785_propagation_latest_frame.js b/tests/cypress/integration/issue_1785_propagation_latest_frame.js new file mode 100644 index 00000000..e9b940ba --- /dev/null +++ b/tests/cypress/integration/issue_1785_propagation_latest_frame.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Check propagation work from the latest frame', () => { + + const issueId = '1785' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const images = [`image_${issueId}_1.png`, + `image_${issueId}_2.png`, + `image_${issueId}_3.png`] + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'gray' + const archiveName = `images_issue_${issueId}.zip` + const archivePath = `cypress/fixtures/${archiveName}` + const imagesFolder = `cypress/fixtures/image_issue_${issueId}` + const directoryToArchive = imagesFolder + + before(() => { + cy.visit('auth/login') + cy.login() + for (let img of images) { + cy.imageGenerator(imagesFolder, img, width, height, color, posX, posY, labelName) + } + cy.createZipArchive(directoryToArchive, archivePath) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, archiveName) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Go to the last frame', () => { + cy.get('.cvat-player-last-button') + .click() + cy.get('.cvat-player-frame-selector').within(() => { + cy.get('input[role="spinbutton"]') + .should('have.value', '2') + }) + }) + it('Create a shape', () => { + cy.createShape(309, 431, 616, 671) + }) + it('Try to propagate', () => { + cy.get('#cvat_canvas_shape_1').trigger('mousemove') + cy.get('body').type('{ctrl}b') + cy.get('.ant-modal-content') + .find('.ant-btn-primary') + .click() + cy.get('.ant-notification-notice').should('not.exist') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1810_login_logout.js b/tests/cypress/integration/issue_1810_login_logout.js new file mode 100644 index 00000000..44a943e9 --- /dev/null +++ b/tests/cypress/integration/issue_1810_login_logout.js @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('When clicking on the Logout button, get the user session closed.', () => { + + const issueId = '1810' + + before(() => { + cy.visit('auth/login') + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Login', () => { + cy.login() + cy.url().should('include', '/tasks') + }) + it('Logout', () => { + cy.logout() + cy.url().should('include', '/auth/login') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1819_first_part_splitted_track_visible.js b/tests/cypress/integration/issue_1819_first_part_splitted_track_visible.js new file mode 100644 index 00000000..a4e42935 --- /dev/null +++ b/tests/cypress/integration/issue_1819_first_part_splitted_track_visible.js @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('First part of a splitted track is visible', () => { + + const issueId = '1819' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const images = [`image_${issueId}_1.png`, + `image_${issueId}_2.png`, + `image_${issueId}_3.png`] + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'gray' + const archiveName = `images_issue_${issueId}.zip` + const archivePath = `cypress/fixtures/${archiveName}` + const imagesFolder = `cypress/fixtures/image_issue_${issueId}` + const directoryToArchive = imagesFolder + + before(() => { + cy.visit('auth/login') + cy.login() + for (let img of images) { + cy.imageGenerator(imagesFolder, img, width, height, color, posX, posY, labelName) + } + cy.createZipArchive(directoryToArchive, archivePath) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, archiveName) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Create a track', () => { + cy.createTrack(309, 431, 616, 671) + }) + it('Go next with a step', () => { + cy.get('.cvat-player-forward-button') + .click() + cy.get('.cvat-player-frame-selector').within(() => { + cy.get('input[role="spinbutton"]') + .should('have.value', '2') + }) + }) + it('Split track', () => { + cy.get('body') + .type('{alt}m') + cy.get('#cvat_canvas_shape_1') + .trigger('mousemove', {which: 1}) + .trigger('click', {which: 1}) + }) + it('Go to previous frame', () => { + cy.get('.cvat-player-previous-button') + .click() + cy.get('.cvat-player-frame-selector').within(() => { + cy.get('input[role="spinbutton"]') + .should('have.value', '1') + }) + }) + it('First part of a splitted track is visible', () => { + cy.get('#cvat_canvas_shape_2') + .should('be.visible') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1841_hidden_points_cuboids_grouping.js b/tests/cypress/integration/issue_1841_hidden_points_cuboids_grouping.js new file mode 100644 index 00000000..890f36fb --- /dev/null +++ b/tests/cypress/integration/issue_1841_hidden_points_cuboids_grouping.js @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Hidden objects mustn\'t consider when we want to group visible objects only and use an grouping area for it.', () => { + + const issueId = '1841' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const image = `image_${issueId}.png` + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'white' + let bgcolor = '' + + before(() => { + cy.visit('auth/login') + cy.login() + cy.imageGenerator('cypress/fixtures', image, width, height, color, posX, posY, labelName) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, image) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Change appearance to "Group"', () => { + cy.changeAppearance('Group') + }) + it('Create three points as different objects', () => { + cy.createPoint(300, 410) + cy.get('#cvat-objects-sidebar-state-item-1') + .should('contain', '1').and('contain', 'POINTS SHAPE') + cy.createPoint(350, 410) + cy.get('#cvat-objects-sidebar-state-item-2') + .should('contain', '2').and('contain', 'POINTS SHAPE') + cy.createPoint(400, 410) + cy.get('#cvat-objects-sidebar-state-item-3') + .should('contain', '3').and('contain', 'POINTS SHAPE') + .should('have.attr', 'style').then(($bgcolor) => { + bgcolor = $bgcolor // Get style attr "background-color" + }) + }) + it('Hide the last point', () => { + cy.get('#cvat-objects-sidebar-state-item-3') + .find('.anticon-eye') + .click() + cy.get('#cvat-objects-sidebar-state-item-3') + .find('.anticon-eye-invisible') + .should('exist') + }) + it('Group the created points', () => { + cy.shapeGrouping(250, 380, 430, 450) + }) + it('The hidden point is not grouping', () => { + cy.get('#cvat-objects-sidebar-state-item-3') + .should('have.attr', 'style', bgcolor) // "background-color" should not be changed + }) + }) +}) diff --git a/tests/cypress/integration/issue_1870_cursor_not_jump_to_end.js b/tests/cypress/integration/issue_1870_cursor_not_jump_to_end.js new file mode 100644 index 00000000..e44ea21c --- /dev/null +++ b/tests/cypress/integration/issue_1870_cursor_not_jump_to_end.js @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Checks that the cursor doesn\'t automatically jump to the end of a word when the attribute value changes', () => { + + const issueId = '1870' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'text' + const image = `image_${issueId}.png` + const newLabelAttrValue = 'teeext' + const width = 800 + const height = 800 + const posX=10 + const posY=10 + const color='gray' + + before(() => { + cy.visit('auth/login') + cy.login() + cy.imageGenerator('cypress/fixtures', image, width, height, color, posX, posY, labelName) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, image) + cy.openTaskJob(taskName) + cy.createShape(309, 431, 616, 671) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Enter 2 characters in the middle of the word attribute value and check the result', () => { + cy.get('#cvat-objects-sidebar-state-item-1') + .find('.ant-collapse-item') + .click() + .find('.cvat-object-item-text-attribute') + .type('{leftarrow}{leftarrow}ee') + .should('have.value', newLabelAttrValue) + }) + }) +}) diff --git a/tests/cypress/integration/issue_1882_polygon_interpolation.js b/tests/cypress/integration/issue_1882_polygon_interpolation.js new file mode 100644 index 00000000..7519bbbe --- /dev/null +++ b/tests/cypress/integration/issue_1882_polygon_interpolation.js @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('The points of the previous polygon mustn\'t appear while polygon\'s interpolation.', () => { + + const issueId = '1882' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const image = `image_${issueId}.png` + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'white' + + before(() => { + cy.visit('auth/login') + cy.login() + cy.imageGenerator('cypress/fixtures', image, width, height, color, posX, posY, labelName) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, image) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Create a polygon', () => { + cy.createPolygon('Track', [ + {x: 309, y: 431}, + {x: 360, y: 500}, + {x: 320, y: 300}, + ]) + cy.get('#cvat-objects-sidebar-state-item-1') + .should('contain', '1').and('contain', 'POLYGON TRACK') + }) + it('Redraw the polygon', () => { + cy.get('#cvat_canvas_shape_1') + .trigger('mousemove', {force: true}) + .trigger('keydown', {key: 'n', shiftKey: true}) + .trigger('keyup', {force: true}, {key: 'n', shiftKey: true}) + cy.createPolygon('Track', [ + {x: 359, y: 431}, + {x: 410, y: 500}, + {x: 370, y: 300}, + ], + false, true) + }) + it('Activate auto bordering mode', () => { + cy.openSettings() + cy.get('.ant-modal-content').within(() => { + cy.contains('Workspace').click() + cy.get('.cvat-workspace-settings-autoborders').within(() => { + cy.get('[type="checkbox"]').check() + }) + }) + cy.closeSettings() + }) + it('Old points invisible', () => { + cy.get('.cvat_canvas_autoborder_point') + .should('not.exist') + }) + }) +}) diff --git a/tests/cypress/integration/issue_1886_point_coordinates_not_duplicated.js b/tests/cypress/integration/issue_1886_point_coordinates_not_duplicated.js new file mode 100644 index 00000000..af3b6653 --- /dev/null +++ b/tests/cypress/integration/issue_1886_point_coordinates_not_duplicated.js @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Point coordinates are not duplicated while polygon\'s interpolation.', () => { + + const issueId = '1886' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const imagesCount = 4 + let images = [] + for ( let i = 1; i <= imagesCount; i++) { + images.push(`image_${issueId}_${i}.png`) + } + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'white' + const archiveName = `images_issue_${issueId}.zip` + const archivePath = `cypress/fixtures/${archiveName}` + const imagesFolder = `cypress/fixtures/image_issue_${issueId}` + const directoryToArchive = imagesFolder + let pointsСoordinates = [] + + before(() => { + cy.visit('auth/login') + cy.login() + for (let img of images) { + cy.imageGenerator(imagesFolder, img, width, height, color, posX, posY, labelName) + } + cy.createZipArchive(directoryToArchive, archivePath) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, archiveName) + cy.openTaskJob(taskName) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Create a polygon', () => { + cy.createPolygon('Track', [ + {x: 300, y: 450}, + {x: 400, y: 450}, + {x: 400, y: 550}, + ]) + cy.get('#cvat-objects-sidebar-state-item-1') + .should('contain', '1').and('contain', 'POLYGON TRACK') + }) + it('Go next with a step', () => { + cy.get('.cvat-player-forward-button').click() + cy.get('.cvat-player-frame-selector').within(() => { + cy.get('input[role="spinbutton"]') + .should('have.value', '3') + }) + }) + it('Set a keyframe for the polygon', () => { + cy.get('#cvat-objects-sidebar-state-item-1').within(() => { + cy.get('[data-icon="star"]').click() + }) + }) + it('Go to previous frame and getting point`s coordinates', () => { + cy.get('.cvat-player-previous-button').click() + cy.get('.cvat-player-frame-selector').within(() => { + cy.get('input[role="spinbutton"]') + .should('have.value', '2') + }) + cy.get('#cvat_canvas_shape_1').should('have.prop', 'animatedPoints') + .then(($pointsСoordinates) => { + for (let i of $pointsСoordinates) { + pointsСoordinates.push(`${i.x}, ${i.y}`) + } + }) + }) + it('The coordinates of the points are not duplicated', () => { + for(let i = 0; i < pointsСoordinates.length - 1; i++) { + cy.expect(pointsСoordinates[i]).not.equal(pointsСoordinates[i+1]) + } + }) + }) +}) diff --git a/tests/cypress/integration/issue_1919_check_text_attr.js b/tests/cypress/integration/issue_1919_check_text_attr.js new file mode 100644 index 00000000..ea6fca77 --- /dev/null +++ b/tests/cypress/integration/issue_1919_check_text_attr.js @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Check label attribute changes', () => { + + const issueId = '1919' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const image = `image_${issueId}.png` + const newLabelAttrValue = 'New attribute value' + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'gray' + + before(() => { + cy.visit('auth/login') + cy.login() + cy.imageGenerator('cypress/fixtures', image, width, height, color, posX, posY, labelName) + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, image) + cy.openTaskJob(taskName) + cy.createShape(309, 431, 616, 671) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Open object menu', () => { + cy.get('#cvat_canvas_shape_1').trigger('mousemove').rightclick() + }) + it('Open object menu details', () => { + cy.get('.cvat-canvas-context-menu') + .contains('Details') + .click() + }) + it('Clear field of text attribute and write new value', () => { + cy.get('.cvat-canvas-context-menu') + .find('.cvat-object-item-text-attribute') + .should('have.value', textDefaultValue) + .clear() + .type(newLabelAttrValue) + }) + it('Check what value of right panel is changed too', () => { + cy.get('#cvat-objects-sidebar-state-item-1') + .find('.cvat-object-item-text-attribute') + .should('have.value', newLabelAttrValue) + }) + }) +}) diff --git a/tests/cypress/integration/issue_1944_loading_screen_switch_job.js b/tests/cypress/integration/issue_1944_loading_screen_switch_job.js new file mode 100644 index 00000000..b68e1272 --- /dev/null +++ b/tests/cypress/integration/issue_1944_loading_screen_switch_job.js @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +context('Being able to return to the job list for a task and start a new job without an infinite loading screen.', () => { + + const issueId = '1944' + const labelName = `Issue ${issueId}` + const taskName = `New annotation task for ${labelName}` + const attrName = `Attr for ${labelName}` + const textDefaultValue = 'Some default value for type Text' + const imagesCount = 4 + let images = [] + for ( let i = 1; i <= imagesCount; i++) { + images.push(`image_${issueId}_${i}.png`) + } + const width = 800 + const height = 800 + const posX = 10 + const posY = 10 + const color = 'gray' + const archiveName = `images_issue_${issueId}.zip` + const archivePath = `cypress/fixtures/${archiveName}` + const imagesFolder = `cypress/fixtures/image_issue_${issueId}` + const directoryToArchive = imagesFolder + const multiJobs = true + + before(() => { + cy.visit('auth/login') + cy.login() + for (let img of images) { + cy.imageGenerator(imagesFolder, img, width, height, color, posX, posY, labelName) + } + cy.createZipArchive(directoryToArchive, archivePath) + }) + + describe(`Testing issue "${issueId}"`, () => { + it('Create a multijob task', () => { + cy.createAnnotationTask(taskName, labelName, attrName, textDefaultValue, archiveName, multiJobs) + }) + it('Open the task. Open first job', () => { + cy.openTaskJob(taskName) + cy.get('input[role="spinbutton"]') + .should('have.value', '0') + }) + it('Return to tasks page', () => { + cy.get('[value="tasks"]').click() + cy.url().should('include', '/tasks').and('not.contain', '/jobs') + }) + it('Open the task. Open second job', () => { + cy.openTaskJob(taskName, 1) + cy.get('.cvat-annotation-header') + .should('exist') + cy.get('input[role="spinbutton"]') + .should('have.value', '1') + }) + }) +}) diff --git a/tests/cypress/plugins/createZipArchive/addPlugin.js b/tests/cypress/plugins/createZipArchive/addPlugin.js new file mode 100644 index 00000000..9c8dae40 --- /dev/null +++ b/tests/cypress/plugins/createZipArchive/addPlugin.js @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +exports.createZipArchive = createZipArchive + +const archiver = require('archiver') +const fs = require('fs-extra') + +function createZipArchive(args) { + const directoryToArchive = args.directoryToArchive + const output = fs.createWriteStream(args.arhivePath) + const archive = archiver('zip', { + gzip: true, + zlib: { level: 9 } + }) + + archive.on('error', function(err) { + throw err + }) + + archive.pipe(output) + + archive.directory(`${directoryToArchive}/`, false) + archive.finalize() + + return fs.pathExists(archive) +} diff --git a/tests/cypress/plugins/createZipArchive/createZipArchiveCommand.js b/tests/cypress/plugins/createZipArchive/createZipArchiveCommand.js new file mode 100644 index 00000000..9dbcf0a9 --- /dev/null +++ b/tests/cypress/plugins/createZipArchive/createZipArchiveCommand.js @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +Cypress.Commands.add('createZipArchive', function (directoryToArchive, arhivePath) { + return cy.task('createZipArchive', { + directoryToArchive: directoryToArchive, + arhivePath: arhivePath + }) +}) diff --git a/tests/cypress/plugins/imageGenerator/addPlugin.js b/tests/cypress/plugins/imageGenerator/addPlugin.js new file mode 100644 index 00000000..30d3f48d --- /dev/null +++ b/tests/cypress/plugins/imageGenerator/addPlugin.js @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +exports.imageGenerator = imageGenerator + +const jimp = require('jimp') +const path = require('path') + +function imageGenerator(args) { + const directory = args.directory + const fileName = args.fileName + const width = args.width + const height = args.height + const color = args.color + const posX = args.posX + const posY = args.posY + const message = args.message + const file = path.join(directory, fileName) + return new Promise((resolve, reject) => { + const image = new jimp(width, height, color, function (err, image) { + if (err) reject(err) + jimp.loadFont(jimp.FONT_SANS_64_BLACK, function (err, font) { + if (err) reject(err) + image.print(font, Number(posX), Number(posY), message) + .write(file) + }) + }) + setTimeout(() => resolve(null), '1000') + }) +} diff --git a/tests/cypress/plugins/imageGenerator/imageGeneratorCommand.js b/tests/cypress/plugins/imageGenerator/imageGeneratorCommand.js new file mode 100644 index 00000000..b51b6e23 --- /dev/null +++ b/tests/cypress/plugins/imageGenerator/imageGeneratorCommand.js @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +Cypress.Commands.add('imageGenerator', (directory, + fileName, + width, + height, + color, + posX, + posY, + message) => { + return cy.task('imageGenerator', { + directory: directory, + fileName: fileName, + width: width, + height: height, + color: color, + posX: posX, + posY: posY, + message: message + }) +}) diff --git a/tests/cypress/plugins/index.js b/tests/cypress/plugins/index.js new file mode 100644 index 00000000..8e09a2cf --- /dev/null +++ b/tests/cypress/plugins/index.js @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +const {imageGenerator} = require('../plugins/imageGenerator/addPlugin') +const {createZipArchive} = require('../plugins/createZipArchive/addPlugin') + +module.exports = (on) => { + on('task', {imageGenerator}) + on('task', {createZipArchive}) +} diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js new file mode 100644 index 00000000..981b0de4 --- /dev/null +++ b/tests/cypress/support/commands.js @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/// + +require('cypress-file-upload') +require('../plugins/imageGenerator/imageGeneratorCommand') +require('../plugins/createZipArchive/createZipArchiveCommand') + +Cypress.Commands.add('login', (username=Cypress.env('user'), password=Cypress.env('password')) => { + cy.get('[placeholder="Username"]').type(username) + cy.get('[placeholder="Password"]').type(password) + cy.get('[type="submit"]').click() +}) + +Cypress.Commands.add('logout', (username=Cypress.env('user')) => { + cy.get('.cvat-right-header') + .find('.cvat-header-menu-dropdown') + .should('have.text', username) + .trigger('mouseover', {which: 1}) + cy.get('.anticon-logout') + .click() +}) + +Cypress.Commands.add('createAnnotationTask', (taksName='New annotation task', + labelName='Some label', + attrName='Some attr name', + textDefaultValue='Some default value for type Text', + image='image.png', + multiJobs=false, + segmentSize=1) => { + cy.contains('button', 'Create new task').click() + cy.url().should('include', '/tasks/create') + cy.get('[id="name"]').type(taksName) + cy.contains('button', 'Add label').click() + cy.get('[placeholder="Label name"]').type(labelName) + cy.contains('button', 'Add an attribute').click() + cy.get('[placeholder="Name"]').type(attrName) + cy.get('div[title="Select"]').click() + cy.get('li').contains('Text').click() + cy.get('[placeholder="Default value"]').type(textDefaultValue) + cy.contains('button', 'Done').click() + cy.get('input[type="file"]').attachFile(image, { subjectType: 'drag-n-drop' }); + if (multiJobs) { + cy.contains('Advanced configuration').click() + cy.get('#segmentSize') + .type(segmentSize) + } + cy.contains('button', 'Submit').click() + cy.contains('The task has been created', {timeout: '8000'}) + cy.get('[value="tasks"]').click() + cy.url().should('include', '/tasks?page=') +}) + +Cypress.Commands.add('openTask', (taskName) => { + cy.contains('strong', taskName) + .parents('.cvat-tasks-list-item') + .contains('a', 'Open') + .click() +}) + +Cypress.Commands.add('openJob', (jobNumber=0) => { + cy.get('.ant-table-tbody') + .find('tr') + .eq(jobNumber) + .contains('a', 'Job #') + .click() + cy.url().should('include', '/jobs') +}) + +Cypress.Commands.add('openTaskJob', (taskName, jobNumber=0) => { + cy.openTask(taskName) + cy.openJob(jobNumber) +}) + +Cypress.Commands.add('createShape', (firstX, firstY, lastX, lastY) => { + cy.get('.cvat-draw-rectangle-control').click() + cy.get('.cvat-draw-shape-popover-content') + .find('button') + .contains('Shape') + .click({force: true}) + cy.get('.cvat-canvas-container') + .click(firstX, firstY) + cy.get('.cvat-canvas-container') + .click(lastX, lastY) +}) + +Cypress.Commands.add('createTrack', (firstX, firstY, lastX, lastY) => { + cy.get('.cvat-draw-rectangle-control').click() + cy.get('.cvat-draw-shape-popover-content') + .find('button') + .contains('Track') + .click({force: true}) + cy.get('.cvat-canvas-container') + .click(firstX, firstY) + cy.get('.cvat-canvas-container') + .click(lastX, lastY) +}) + +Cypress.Commands.add('createPoint', (posX, posY) => { + cy.get('.cvat-draw-points-control').click() + cy.get('.cvat-draw-shape-popover-content') + .find('button') + .contains('Shape') + .click({force: true}) + cy.get('.cvat-canvas-container') + .click(posX, posY) + .trigger('keydown', {key: 'n'}) + .trigger('keyup', {key: 'n'}) +}) + +Cypress.Commands.add('changeAppearance', (colorBy) => { + cy.get('.cvat-objects-appearance-content').within(() => { + cy.get('[type="radio"]') + .check(colorBy, {force: true}) + }) +}) + +Cypress.Commands.add('shapeGrouping', (firstX, firstY, lastX, lastY) => { + cy.get('.cvat-canvas-container') + .trigger('keydown', {key: 'g'}) + .trigger('keyup', {key: 'g'}) + .trigger('mousedown', firstX, firstY, {which: 1}) + .trigger('mousemove', lastX, lastY) + .trigger('mouseup', lastX, lastY) + .trigger('keydown', {key: 'g'}) + .trigger('keyup', {key: 'g'}) +}) + +Cypress.Commands.add('createPolygon', ( mode, + pointsMap, + complete=true, + reDraw=false) => { + if (!reDraw) { + cy.get('.cvat-draw-polygon-control').click() + cy.contains('Draw new polygon') + .parents('.cvat-draw-shape-popover-content') + .within(() => { + cy.get('button') + .contains(mode) + .click({force: true}) + }) + } + pointsMap.forEach(element => { + cy.get('.cvat-canvas-container') + .click(element.x, element.y) + }) + if (complete) { + cy.get('.cvat-canvas-container') + .trigger('keydown', {key: 'n'}) + .trigger('keyup', {key: 'n'}) + } +}) + +Cypress.Commands.add('openSettings', () => { + cy.get('.cvat-right-header') + .find('.cvat-header-menu-dropdown') + .trigger('mouseover', {which: 1}) + cy.get('.anticon-setting') + .click() +}) + +Cypress.Commands.add('closeSettings', () => { + cy.get('.ant-modal-content') + .should('contain', 'Settings') + .within(() => { + cy.contains('button', 'Close').click() + }) +}) diff --git a/tests/cypress/support/index.js b/tests/cypress/support/index.js new file mode 100644 index 00000000..bf9295aa --- /dev/null +++ b/tests/cypress/support/index.js @@ -0,0 +1,7 @@ +/* + * Copyright (C) 2020 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +import './commands' diff --git a/tests/eslintrc.conf.js b/tests/eslintrc.conf.js deleted file mode 100644 index cd04f937..00000000 --- a/tests/eslintrc.conf.js +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -module.exports = { - 'env': { - 'browser': true, - 'es6': true, - 'jquery': true, - 'qunit': true, - }, - "extends": "eslint:recommended", - 'rules': { - 'indent': [ - 'warn', - 4 - ], - 'linebreak-style': [ - 'error', - 'unix' - ], - 'semi': [ - 'warn' - ], - 'no-extra-semi': [ - 'warn' - ], - 'no-console': [ - 'warn' - ], - }, - 'globals': { - // from attributeAnnotationMode.js - 'AAMController': true, - 'AAMModel': true, - 'AAMView': true, - 'AAMUndefinedKeyword': true, - // from annotationParser.js - 'AnnotationParser': true, - // from annotationUI.js - 'callAnnotationUI': true, - 'blurAllElements': true, - 'drawBoxSize': true, - 'copyToClipboard': true, - // from base.js - 'showMessage': true, - 'showOverlay': true, - 'confirm': true, - 'dumpAnnotationRequest': true, - 'createExportContainer': true, - 'ExportType': true, - 'getExportTargetContainer': true, - // from idGenerator.js - 'IncrementIdGenerator': true, - 'ConstIdGenerator': true, - // from shapeCollection.js - 'ShapeCollectionModel': true, - 'ShapeCollectionController': true, - 'ShapeCollectionView': true, - // from shapeCreator.js - 'ShapeCreatorModel': true, - 'ShapeCreatorController': true, - 'ShapeCreatorView': true, - // from shapeGrouper.js - 'ShapeGrouperModel': true, - 'ShapeGrouperController': true, - 'ShapeGrouperView': true, - // from labelsInfo.js - 'LabelsInfo': true, - // from listener.js - 'Listener': true, - // from logger.js - 'Logger': true, - // from shapeMerger.js - 'ShapeMergerModel': true, - 'ShapeMergerController': true, - 'ShapeMergerView': true, - // from shapes.js - 'PolyShapeModel': true, - 'PolyShapeView': true, - 'buildShapeModel': true, - 'buildShapeController': true, - 'buildShapeView': true, - 'STROKE_WIDTH': true, - 'POINT_RADIUS': true, - 'AREA_TRESHOLD': true, - 'SELECT_POINT_STROKE_WIDTH': true, - // from mousetrap.js - 'Mousetrap': true, - // from platform.js - 'platform': true, - // from player.js - 'PlayerController': true, - 'PlayerModel': true, - 'PlayerView': true, - // from server.js - 'serverRequest': true, - 'saveJobRequest': true, - // from shapeBuffer.js - 'ShapeBufferController': true, - 'ShapeBufferModel': true, - 'ShapeBufferView': true, - // from trackFilter.js - 'FilterModel': true, - 'FilterController': true, - 'FilterView': true, - // from shapeSplitter.js - 'ShapeSplitter': true, - // from userConfig.js - 'Config': true, - // from cookies.js - 'Cookies': true, - // from dashboard django template - 'maxUploadCount': true, - 'maxUploadSize': true, - // from SVG.js - 'SVG': true, - // from history.js - 'HistoryModel': true, - 'HistoryController': true, - 'HistoryView': true, - // from polyshapeEditor.js - 'PolyshapeEditorModel': true, - 'PolyshapeEditorController': true, - 'PolyshapeEditorView': true, - // from coordinateTranslator - 'CoordinateTranslator': true, - }, -}; diff --git a/tests/karma.conf.js b/tests/karma.conf.js deleted file mode 100644 index 8e28f656..00000000 --- a/tests/karma.conf.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -const path = require('path'); -module.exports = function(config) { - config.set({ - basePath: path.join(process.env.HOME, 'cvat/apps/'), - frameworks: ['qunit'], - files: [ - 'engine/static/engine/js/labelsInfo.js', - 'engine/static/engine/js/annotationParser.js', - 'engine/static/engine/js/listener.js', - 'engine/static/engine/js/player.js', - 'engine/static/engine/js/shapes.js', - 'engine/static/engine/js/qunitTests.js', - ], - port: 9876, - colors: true, - autoWatch: false, - browsers: ['ChromeNoSandbox'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Concurrency level - // how many browser should be started simultanous - concurrency: Infinity, - - preprocessors: { - '**/!(qunitTests).js': ['coverage'] - }, - - reporters: ['progress', 'junit', 'coverage', 'coveralls'], - - coverageReporter: { - dir: path.join(process.env.HOME, 'media/coverage'), - reporters: [ - { type: 'html', subdir: '.' }, { type: 'lcov', subdir: '.' } - ], - instrumenterOptions: { - istanbul: { noCompact: true } - } - }, - - junitReporter: { - outputDir: path.join(process.env.HOME, 'media/junit'), - outputFile: undefined, - useBrowserName: true, - nameFormatter: undefined, - classNameFormatter: undefined, - properties: {}, - xmlVersion: null - }, - - customLaunchers: { - ChromeNoSandbox: { - base: 'ChromeHeadless', - flags: ['--no-sandbox'] - } - }, - - logLevel: config.LOG_DEBUG - }); -} diff --git a/tests/package-lock.json b/tests/package-lock.json new file mode 100644 index 00000000..3e0194dd --- /dev/null +++ b/tests/package-lock.json @@ -0,0 +1,2461 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@cypress/listr-verbose-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", + "integrity": "sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-cursor": "^1.0.2", + "date-fns": "^1.27.2", + "figures": "^1.7.0" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "@cypress/request": { + "version": "2.88.5", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz", + "integrity": "sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@jimp/bmp": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.14.0.tgz", + "integrity": "sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0", + "bmp-js": "^0.1.0" + } + }, + "@jimp/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.14.0.tgz", + "integrity": "sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^9.0.0", + "load-bmfont": "^1.3.1", + "mkdirp": "^0.5.1", + "phin": "^2.9.1", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.4.1" + } + }, + "@jimp/custom": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.14.0.tgz", + "integrity": "sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/core": "^0.14.0" + } + }, + "@jimp/gif": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.14.0.tgz", + "integrity": "sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0", + "gifwrap": "^0.9.2", + "omggif": "^1.0.9" + } + }, + "@jimp/jpeg": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.14.0.tgz", + "integrity": "sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0", + "jpeg-js": "^0.4.0" + } + }, + "@jimp/plugin-blit": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz", + "integrity": "sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-blur": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz", + "integrity": "sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-circle": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz", + "integrity": "sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-color": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.14.0.tgz", + "integrity": "sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0", + "tinycolor2": "^1.4.1" + } + }, + "@jimp/plugin-contain": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz", + "integrity": "sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-cover": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz", + "integrity": "sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-crop": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz", + "integrity": "sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-displace": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz", + "integrity": "sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-dither": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz", + "integrity": "sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-fisheye": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz", + "integrity": "sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-flip": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz", + "integrity": "sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-gaussian": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz", + "integrity": "sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-invert": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz", + "integrity": "sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-mask": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz", + "integrity": "sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-normalize": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz", + "integrity": "sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-print": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.14.0.tgz", + "integrity": "sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0", + "load-bmfont": "^1.4.0" + } + }, + "@jimp/plugin-resize": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz", + "integrity": "sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-rotate": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz", + "integrity": "sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-scale": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz", + "integrity": "sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-shadow": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz", + "integrity": "sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugin-threshold": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz", + "integrity": "sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0" + } + }, + "@jimp/plugins": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.14.0.tgz", + "integrity": "sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/plugin-blit": "^0.14.0", + "@jimp/plugin-blur": "^0.14.0", + "@jimp/plugin-circle": "^0.14.0", + "@jimp/plugin-color": "^0.14.0", + "@jimp/plugin-contain": "^0.14.0", + "@jimp/plugin-cover": "^0.14.0", + "@jimp/plugin-crop": "^0.14.0", + "@jimp/plugin-displace": "^0.14.0", + "@jimp/plugin-dither": "^0.14.0", + "@jimp/plugin-fisheye": "^0.14.0", + "@jimp/plugin-flip": "^0.14.0", + "@jimp/plugin-gaussian": "^0.14.0", + "@jimp/plugin-invert": "^0.14.0", + "@jimp/plugin-mask": "^0.14.0", + "@jimp/plugin-normalize": "^0.14.0", + "@jimp/plugin-print": "^0.14.0", + "@jimp/plugin-resize": "^0.14.0", + "@jimp/plugin-rotate": "^0.14.0", + "@jimp/plugin-scale": "^0.14.0", + "@jimp/plugin-shadow": "^0.14.0", + "@jimp/plugin-threshold": "^0.14.0", + "timm": "^1.6.1" + } + }, + "@jimp/png": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.14.0.tgz", + "integrity": "sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.14.0", + "pngjs": "^3.3.3" + } + }, + "@jimp/tiff": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.14.0.tgz", + "integrity": "sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw==", + "requires": { + "@babel/runtime": "^7.7.2", + "utif": "^2.0.1" + } + }, + "@jimp/types": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.14.0.tgz", + "integrity": "sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/bmp": "^0.14.0", + "@jimp/gif": "^0.14.0", + "@jimp/jpeg": "^0.14.0", + "@jimp/png": "^0.14.0", + "@jimp/tiff": "^0.14.0", + "timm": "^1.6.1" + } + }, + "@jimp/utils": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.14.0.tgz", + "integrity": "sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A==", + "requires": { + "@babel/runtime": "^7.7.2", + "regenerator-runtime": "^0.13.3" + } + }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, + "@types/sinonjs__fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", + "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", + "dev": true + }, + "@types/sizzle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", + "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", + "dev": true + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true + }, + "arch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz", + "integrity": "sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ==", + "dev": true + }, + "archiver": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.0.tgz", + "integrity": "sha512-AEWhJz6Yi6hWtN1Sqy/H4sZo/lLMJ/NftXxGaDy/TnOMmmjsRaZc/Ts+U4BsPoBQkuunTN6t8hk7iU9A+HBxLw==", + "requires": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.1.2", + "zip-stream": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true, + "requires": { + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "optional": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", + "dev": true + }, + "compress-commons": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", + "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "crc32-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz", + "integrity": "sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw==", + "requires": { + "crc": "^3.4.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "cypress": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.12.1.tgz", + "integrity": "sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q==", + "dev": true, + "requires": { + "@cypress/listr-verbose-renderer": "^0.4.1", + "@cypress/request": "^2.88.5", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "^6.0.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.1.2", + "bluebird": "^3.7.2", + "cachedir": "^2.3.0", + "chalk": "^2.4.2", + "check-more-types": "^2.24.0", + "cli-table3": "~0.5.1", + "commander": "^4.1.1", + "common-tags": "^1.8.0", + "debug": "^4.1.1", + "eventemitter2": "^6.4.2", + "execa": "^1.0.0", + "executable": "^4.1.1", + "extract-zip": "^1.7.0", + "fs-extra": "^8.1.0", + "getos": "^3.2.1", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.2", + "lazy-ass": "^1.6.0", + "listr": "^0.14.3", + "lodash": "^4.17.19", + "log-symbols": "^3.0.0", + "minimist": "^1.2.5", + "moment": "^2.27.0", + "ospath": "^1.2.2", + "pretty-bytes": "^5.3.0", + "ramda": "~0.26.1", + "request-progress": "^3.0.0", + "supports-color": "^7.1.0", + "tmp": "~0.1.0", + "untildify": "^4.0.0", + "url": "^0.11.0", + "yauzl": "^2.10.0" + } + }, + "cypress-file-upload": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-4.0.7.tgz", + "integrity": "sha512-rFFmnoZ2bWyWFpSV09AhkSUgYEiVy70pcQ6nf/mGTMTrVHvKCCCIfRu3TbgVYHbgBq+0hqjfjQrtz4IbgH7qZA==", + "dev": true, + "requires": { + "mime": "^2.4.4" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eventemitter2": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz", + "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "requires": { + "pify": "^2.2.0" + } + }, + "exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "requires": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-type": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "requires": { + "async": "^3.2.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "gifwrap": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.2.tgz", + "integrity": "sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA==", + "requires": { + "image-q": "^1.1.1", + "omggif": "^1.0.10" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, + "global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "dev": true, + "requires": { + "ini": "^1.3.5" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "image-q": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz", + "integrity": "sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=" + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, + "is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "requires": { + "symbol-observable": "^1.1.0" + } + }, + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jimp": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.14.0.tgz", + "integrity": "sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA==", + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/custom": "^0.14.0", + "@jimp/plugins": "^0.14.0", + "@jimp/types": "^0.14.0", + "regenerator-runtime": "^0.13.3" + } + }, + "jpeg-js": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.1.tgz", + "integrity": "sha512-jA55yJiB5tCXEddos8JBbvW+IMrqY0y1tjjx9KNVtA+QPmu7ND5j0zkKopClpUTsaETL135uOM2XfcYG4XRjmw==" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + } + }, + "listr": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "dev": true, + "requires": { + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + } + }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true + }, + "listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "dependencies": { + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + } + } + }, + "load-bmfont": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", + "requires": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "dependencies": { + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU=" + }, + "parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" + }, + "parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "requires": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "parse-headers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", + "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=", + "requires": { + "pngjs": "^3.0.0" + } + }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, + "pretty-bytes": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz", + "integrity": "sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==", + "dev": true + }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "readdir-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.0.0.tgz", + "integrity": "sha512-km0DIcwQVZ1ZUhXhMWpF74/Wm5aFEd5/jDiVWF1Hkw2myPQovG8vCQ8+FQO2KXE9npQQvCnAMZhhWuUee4WcCQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "dev": true, + "requires": { + "throttleit": "^1.0.0" + } + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rxjs": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", + "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + } + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "tar-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", + "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, + "timm": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.6.2.tgz", + "integrity": "sha512-IH3DYDL1wMUwmIlVmMrmesw5lZD6N+ZOAFWEyLrtpoL9Bcrs9u7M/vyOnHzDD2SMs4irLkVjqxZbHrXStS/Nmw==" + }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "utif": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", + "requires": { + "pako": "^1.0.5" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xhr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "requires": { + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig=" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "zip-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", + "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", + "requires": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + } + } +} diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 00000000..5861f8d3 --- /dev/null +++ b/tests/package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "cypress": "^4.12.1", + "cypress-file-upload": "^4.0.7" + }, + "dependencies": { + "archiver": "^5.0.0", + "jimp": "^0.14.0" + } +} diff --git a/tests/run_js_linter.sh b/tests/run_js_linter.sh deleted file mode 100644 index a0685185..00000000 --- a/tests/run_js_linter.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# - -eslint -c ~/tests/eslintrc.conf.js -f ~/tests/node_modules/eslint-detailed-reporter/lib/detailed-multi.js \ - ~/cvat/apps/**/static/**/js/*.js -o ./report.html -ret_code=$? - -mkdir -p ~/media/eslint -mv -t ~/media/eslint main.js styles.css report.html - -exit $ret_code diff --git a/utils/README.md b/utils/README.md index a768eedf..48b87c96 100644 --- a/utils/README.md +++ b/utils/README.md @@ -3,6 +3,6 @@ ## Description -This folder contains some useful utilities for Computer Vision Annotation Tool (CVAT). To read about a certain utility please choose a link: - [Auto Annotation Runner](auto_annotation/README.md) +This folder contains some useful utilities for Computer Vision Annotation Tool (CVAT). +To read about a certain utility please choose a link: - [Command line interface for working with CVAT tasks](cli/README.md) diff --git a/utils/auto_annotation/README.md b/utils/auto_annotation/README.md deleted file mode 100644 index 4bc64654..00000000 --- a/utils/auto_annotation/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# Auto Annotation Runner - -A small command line program to test and run AutoAnnotation Scripts. - -## Instructions - -There are two modes to run this script in. If you already have a model uploaded into the server, and you're having -issues with running it in production, you can pass in the model name and a task id that you want to test against. - -```shell -# Note that this module can be found in cvat/utils/auto_annotation/run_model.py -$ python /path/to/run_model.py --model-name mymodel --task-id 4 -``` - -If you're running in docker, this can be useful way to debug your model. - -``` shell -$ docker exec -it cvat bash -ic 'python3 ~/cvat/apps/auto_annotation/run_model.py --model-name my-model --task-id 4 -``` - -If you are developing an auto annotation model or you can't get something uploaded into the server, -then you'll need to specify the individual inputs. - -```shell -# Note that this module can be found in cvat/utils/auto_annotation/run_model.py -$ python path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json -``` - -Some programs need to run unrestricted or as an administer. Use the `--unrestriced` flag to simulate. - -You can pass image files in to fully simulate your findings. Images are passed in as a list - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg -``` - -Additionally, it's sometimes useful to visualize your images. -Use the `--show-images` flag to have each image with the annotations pop up. - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images -``` - -If you'd like to see the labels printed on the image, use the `--show-labels` flag - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images \ - --show-labels -``` - -There's a command that let's you scan quickly by setting the length of time (in milliseconds) to display each image. -Use the `--show-image-delay` flag and set the appropriate time. -In this example, 2000 milliseconds is 2 seconds for each image. - -```shell -# Display each image in a window for 2 seconds -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images \ - --show-image-delay 2000 -``` - -Visualization isn't always enough. -The CVAT has a serialization step that can throw errors on model upload even after successful visualization. -You must install the necessary packages installed, but then you can add the `--serialize` command to ensure that your -results will serialize correctly. - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --serialize -``` diff --git a/utils/auto_annotation/run_model.py b/utils/auto_annotation/run_model.py deleted file mode 100644 index 9df63528..00000000 --- a/utils/auto_annotation/run_model.py +++ /dev/null @@ -1,263 +0,0 @@ -import os -import sys -import json -import argparse -import random -import logging -import fnmatch -from operator import xor - -import numpy as np -import cv2 - -work_dir = os.path.dirname(os.path.abspath(__file__)) -cvat_dir = os.path.join(work_dir, '..', '..') - -sys.path.insert(0, cvat_dir) - -from cvat.apps.auto_annotation.inference import run_inference_engine_annotation - - -def _get_kwargs(): - parser = argparse.ArgumentParser() - parser.add_argument('--py', help='Path to the python interpt file') - parser.add_argument('--xml', help='Path to the xml file') - parser.add_argument('--bin', help='Path to the bin file') - parser.add_argument('--json', help='Path to the JSON mapping file') - - parser.add_argument('--model-name', help='Name of the model in the Model Manager') - parser.add_argument('--task-id', type=int, help='ID task used to test the model') - - parser.add_argument('--restricted', dest='restricted', action='store_true') - parser.add_argument('--unrestricted', dest='restricted', action='store_false') - parser.add_argument('--image-files', nargs='*', help='Paths to image files you want to test') - - parser.add_argument('--show-images', action='store_true', help='Show the results of the annotation in a window') - parser.add_argument('--show-image-delay', default=0, type=int, help='Displays the images for a set duration in milliseconds, default is until a key is pressed') - parser.add_argument('--serialize', default=False, action='store_true', help='Try to serialize the result') - parser.add_argument('--show-labels', action='store_true', help='Show the labels on the window') - - return vars(parser.parse_args()) - -def _init_django(settings): - import django - os.environ['DJANGO_SETTINGS_MODULE'] = settings - django.setup() - -def random_color(): - rgbl=[255,0,0] - random.shuffle(rgbl) - return tuple(rgbl) - - -def pairwise(iterable): - result = [] - for i in range(0, len(iterable) - 1, 2): - result.append((iterable[i], iterable[i+1])) - return np.array(result, dtype=np.int32) - -def find_min_y(array): - min_ = sys.maxsize - index = None - for i, pair in enumerate(array): - if pair[1] < min_: - min_ = pair[1] - index = i - - return array[index] - -def _get_docker_files(model_name: str, task_id: int): - _init_django('cvat.settings.development') - - from cvat.apps.auto_annotation.models import AnnotationModel - from cvat.apps.engine.models import Task as TaskModel - - task = TaskModel(pk=task_id) - model = AnnotationModel.objects.get(name=model_name) - - images_dir = task.data.get_data_dirname() - - py_file = model.interpretation_file.name - mapping_file = model.labelmap_file.name - xml_file = model.model_file.name - bin_file = model.weights_file.name - - image_files = [] - images_dir = os.path.abspath(images_dir) - for root, _, filenames in os.walk(images_dir): - for filename in fnmatch.filter(filenames, '*.jpg'): - image_files.append(os.path.join(root, filename)) - - return py_file, mapping_file, bin_file, xml_file, image_files - - -def main(): - kwargs = _get_kwargs() - - py_file = kwargs.get('py') - bin_file = kwargs.get('bin') - mapping_file = os.path.abspath(kwargs.get('json')) - xml_file = kwargs.get('xml') - - model_name = kwargs.get('model_name') - task_id = kwargs.get('task_id') - - is_docker = model_name and task_id - - # xor is `exclusive or`. English is: if one or the other but not both - if xor(bool(model_name), bool(task_id)): - logging.critical('Must provide both `--model-name` and `--task-id` together!') - return - - if is_docker: - files = _get_docker_files(model_name, task_id) - py_file = files[0] - mapping_file = files[1] - bin_file = files[2] - xml_file = files[3] - image_files = files[4] - else: - return_ = False - if not py_file: - logging.critical('Must provide --py file!') - return_ = True - if not bin_file: - logging.critical('Must provide --bin file!') - return_ = True - if not xml_file: - logging.critical('Must provide --xml file!') - return_ = True - if not mapping_file: - logging.critical('Must provide --json file!') - return_ = True - - if return_: - return - - if not os.path.isfile(py_file): - logging.critical('Py file not found! Check the path') - return - - if not os.path.isfile(bin_file): - logging.critical('Bin file is not found! Check path!') - return - - if not os.path.isfile(xml_file): - logging.critical('XML File not found! Check path!') - return - - if not os.path.isfile(mapping_file): - logging.critical('JSON file is not found! Check path!') - return - - with open(mapping_file) as json_file: - try: - mapping = json.load(json_file) - except json.decoder.JSONDecodeError: - logging.critical('JSON file not able to be parsed! Check file') - return - - try: - mapping = mapping['label_map'] - except KeyError: - logging.critical("JSON Mapping file must contain key `label_map`!") - logging.critical("Exiting") - return - - mapping = {int(k): v for k, v in mapping.items()} - - restricted = kwargs['restricted'] - - if not is_docker: - image_files = kwargs.get('image_files') - - if image_files: - image_data = [cv2.imread(f) for f in image_files] - else: - test_image = np.ones((1024, 1980, 3), np.uint8) * 255 - image_data = [test_image,] - attribute_spec = {} - - results = run_inference_engine_annotation(image_data, - xml_file, - bin_file, - mapping, - attribute_spec, - py_file, - restricted=restricted) - - - logging.warning('Inference didn\'t have any errors.') - show_images = kwargs.get('show_images', False) - - if show_images: - if image_files is None: - logging.critical("Warning, no images provided!") - logging.critical('Exiting without presenting results') - return - - if not results['shapes']: - logging.warning(str(results)) - logging.critical("No objects detected!") - return - - show_image_delay = kwargs['show_image_delay'] - show_labels = kwargs.get('show_labels') - - for index, data in enumerate(image_data): - for detection in results['shapes']: - if not detection['frame'] == index: - continue - points = detection['points'] - label_str = detection['label_id'] - - # Cv2 doesn't like floats for drawing - points = [int(p) for p in points] - color = random_color() - - if detection['type'] == 'rectangle': - cv2.rectangle(data, (points[0], points[1]), (points[2], points[3]), color, 3) - - if show_labels: - cv2.putText(data, label_str, (points[0], points[1] - 7), cv2.FONT_HERSHEY_COMPLEX, 0.6, color, 1) - - elif detection['type'] in ('polygon', 'polyline'): - # polylines is picky about datatypes - points = pairwise(points) - cv2.polylines(data, [points], 1, color) - - if show_labels: - min_point = find_min_y(points) - cv2.putText(data, label_str, (min_point[0], min_point[1] - 7), cv2.FONT_HERSHEY_COMPLEX, 0.6, color, 1) - - cv2.imshow(str(index), data) - cv2.waitKey(show_image_delay) - cv2.destroyWindow(str(index)) - - if kwargs['serialize']: - _init_django('cvat.settings.production') - - from cvat.apps.engine.serializers import LabeledDataSerializer - - # NOTE: We're actually using `run_inference_engine_annotation` - # incorrectly here. The `mapping` dict is supposed to be a mapping - # of integers -> integers and represents the transition from model - # integers to the labels in the database. We're using a mapping of - # integers -> strings. For testing purposes, this shortcut is fine. - # We just want to make sure everything works. Until, that is.... - # we want to test using the label serializer. Then we have to transition - # back to integers, otherwise the serializer complains about have a string - # where an integer is expected. We'll just brute force that. - - for shape in results['shapes']: - # Change the english label to an integer for serialization validation - shape['label_id'] = 1 - - serializer = LabeledDataSerializer(data=results) - - if not serializer.is_valid(): - logging.critical('Data unable to be serialized correctly!') - serializer.is_valid(raise_exception=True) - -if __name__ == '__main__': - main() diff --git a/utils/cli/README.md b/utils/cli/README.md index 07b708d7..f582e2e3 100644 --- a/utils/cli/README.md +++ b/utils/cli/README.md @@ -31,6 +31,8 @@ optional arguments: host (default: localhost) --server-port SERVER_PORT port (default: 8080) + --https + using https connection (default: False) --debug show debug output ``` **Examples** diff --git a/utils/cli/cli.py b/utils/cli/cli.py index 9ac826b4..ae08ad1a 100755 --- a/utils/cli/cli.py +++ b/utils/cli/cli.py @@ -28,7 +28,7 @@ def main(): args = parser.parse_args() config_log(args.loglevel) with requests.Session() as session: - api = CVAT_API_V1('%s:%s' % (args.server_host, args.server_port)) + api = CVAT_API_V1('%s:%s' % (args.server_host, args.server_port), args.https) cli = CLI(session, api, args.auth) try: actions[args.action](cli, **args.__dict__) diff --git a/utils/cli/core/core.py b/utils/cli/core/core.py index f684608e..78a2776c 100644 --- a/utils/cli/core/core.py +++ b/utils/cli/core/core.py @@ -8,6 +8,7 @@ import os import requests from io import BytesIO import mimetypes +from time import sleep from PIL import Image @@ -57,19 +58,42 @@ class CLI(): response = self.session.get(url) response.raise_for_status() - def tasks_create(self, name, labels, bug, resource_type, resources, **kwargs): + def tasks_create(self, name, labels, overlap, segment_size, bug, resource_type, resources, + annotation_path='', annotation_format='CVAT XML 1.1', + completion_verification_period=20, **kwargs): """ Create a new task with the given name and labels JSON and add the files to it. """ url = self.api.tasks data = {'name': name, 'labels': labels, + 'overlap': overlap, + 'segment_size': segment_size, 'bug_tracker': bug, } response = self.session.post(url, json=data) response.raise_for_status() response_json = response.json() log.info('Created task ID: {id} NAME: {name}'.format(**response_json)) - self.tasks_data(response_json['id'], resource_type, resources) + task_id = response_json['id'] + self.tasks_data(task_id, resource_type, resources) + + if annotation_path != '': + url = self.api.tasks_id_status(task_id) + response = self.session.get(url) + response_json = response.json() + + log.info('Awaiting data compression before uploading annotations...') + while response_json['state'] != 'Finished': + sleep(completion_verification_period) + response = self.session.get(url) + response_json = response.json() + logger_string= '''Awaiting compression for task {}. + Status={}, Message={}'''.format(task_id, + response_json['state'], + response_json['message']) + log.info(logger_string) + + self.tasks_upload(task_id, annotation_format, annotation_path, **kwargs) def tasks_delete(self, task_ids, **kwargs): """ Delete a list of tasks, ignoring those which don't exist. """ @@ -135,8 +159,8 @@ class CLI(): while True: response = self.session.put( url, - files={'annotation_file':open(filename, 'rb')} - ) + files={'annotation_file': open(filename, 'rb')} + ) response.raise_for_status() if response.status_code == 201: break @@ -157,8 +181,9 @@ class CLI(): class CVAT_API_V1(): """ Build parameterized API URLs """ - def __init__(self, host): - self.base = 'http://{}/api/v1/'.format(host) + def __init__(self, host, https=False): + prefix = 'https' if https else 'http' + self.base = '{}://{}/api/v1/'.format(prefix, host) @property def tasks(self): @@ -176,6 +201,9 @@ class CVAT_API_V1(): def tasks_id_frame_id(self, task_id, frame_id, quality): return self.tasks_id(task_id) + '/data?type=frame&number={}&quality={}'.format(frame_id, quality) + def tasks_id_status(self, task_id): + return self.tasks_id(task_id) + '/status' + def tasks_id_annotations_format(self, task_id, fileformat): return self.tasks_id(task_id) + '/annotations?format={}' \ .format(fileformat) diff --git a/utils/cli/core/definition.py b/utils/cli/core/definition.py index 7f9eb2db..c30eae07 100644 --- a/utils/cli/core/definition.py +++ b/utils/cli/core/definition.py @@ -78,6 +78,12 @@ parser.add_argument( default='8080', help='port (default: %(default)s)' ) +parser.add_argument( + '--https', + default=False, + action='store_true', + help='using https connection (default: %(default)s)' +) parser.add_argument( '--debug', action='store_const', @@ -106,6 +112,18 @@ task_create_parser.add_argument( type=parse_label_arg, help='string or file containing JSON labels specification' ) +task_create_parser.add_argument( + '--overlap', + default=0, + type=int, + help='the number of intersected frames between different segments' +) +task_create_parser.add_argument( + '--segment_size', + default=0, + type=int, + help='the number of frames in a segment' +) task_create_parser.add_argument( '--bug', default='', @@ -125,6 +143,25 @@ task_create_parser.add_argument( help='list of paths or URLs', nargs='+' ) +task_create_parser.add_argument( + '--annotation_path', + default='', + type=str, + help='path to annotation file' +) +task_create_parser.add_argument( + '--annotation_format', + default='CVAT 1.1', + type=str, + help='format of the annotation file being uploaded, e.g. CVAT 1.1' +) +task_create_parser.add_argument( + '--completion_verification_period', + default=20, + type=int, + help='''number of seconds to wait until checking + if data compression finished (necessary before uploading annotations)''' +) ####################################################################### # Delete @@ -240,4 +277,4 @@ upload_parser.add_argument( type=str, default='CVAT 1.1', help='annotation format (default: %(default)s)' -) +) \ No newline at end of file diff --git a/utils/cli/tests/_test_cli.py b/utils/cli/tests/_test_cli.py index 3977159c..a792633d 100644 --- a/utils/cli/tests/_test_cli.py +++ b/utils/cli/tests/_test_cli.py @@ -27,7 +27,7 @@ class TestCLI(APITestCase): self.taskname = 'test_task' self.cli.tasks_create(self.taskname, [{'name' : 'car'}, {'name': 'person'}], - '', + 0, 0, '', ResourceType.LOCAL, [self.img_file]) # redirect logging to mocked stdout to test program output diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/mappings.json b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/mappings.json deleted file mode 100644 index f6a0aa87..00000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/mappings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label_map": { - "1": "text" - } -} diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/pixel_link_mobilenet_v2.py b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/pixel_link_mobilenet_v2.py deleted file mode 100644 index 77297cab..00000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/pixel_link_mobilenet_v2.py +++ /dev/null @@ -1,192 +0,0 @@ -import cv2 -import numpy as np - - -class PixelLinkDecoder(): - def __init__(self): - four_neighbours = False - if four_neighbours: - self._get_neighbours = self._get_neighbours_4 - else: - self._get_neighbours = self._get_neighbours_8 - self.pixel_conf_threshold = 0.8 - self.link_conf_threshold = 0.8 - - def decode(self, height, width, detections: dict): - self.image_height = height - self.image_width = width - self.pixel_scores = self._set_pixel_scores(detections['pixel_cls/add_2']) - self.link_scores = self._set_link_scores(detections['pixel_link/add_2']) - - self.pixel_mask = self.pixel_scores >= self.pixel_conf_threshold - self.link_mask = self.link_scores >= self.link_conf_threshold - self.points = list(zip(*np.where(self.pixel_mask))) - self.h, self.w = np.shape(self.pixel_mask) - self.group_mask = dict.fromkeys(self.points, -1) - self.bboxes = None - self.root_map = None - self.mask = None - - self._decode() - - def _softmax(self, x, axis=None): - return np.exp(x - self._logsumexp(x, axis=axis, keepdims=True)) - - def _logsumexp(self, a, axis=None, b=None, keepdims=False, return_sign=False): - if b is not None: - a, b = np.broadcast_arrays(a, b) - if np.any(b == 0): - a = a + 0. # promote to at least float - a[b == 0] = -np.inf - - a_max = np.amax(a, axis=axis, keepdims=True) - - if a_max.ndim > 0: - a_max[~np.isfinite(a_max)] = 0 - elif not np.isfinite(a_max): - a_max = 0 - - if b is not None: - b = np.asarray(b) - tmp = b * np.exp(a - a_max) - else: - tmp = np.exp(a - a_max) - - # suppress warnings about log of zero - with np.errstate(divide='ignore'): - s = np.sum(tmp, axis=axis, keepdims=keepdims) - if return_sign: - sgn = np.sign(s) - s *= sgn # /= makes more sense but we need zero -> zero - out = np.log(s) - - if not keepdims: - a_max = np.squeeze(a_max, axis=axis) - out += a_max - - if return_sign: - return out, sgn - else: - return out - - def _set_pixel_scores(self, pixel_scores): - "get softmaxed properly shaped pixel scores" - tmp = np.transpose(pixel_scores, (0, 2, 3, 1)) - return self._softmax(tmp, axis=-1)[0, :, :, 1] - - def _set_link_scores(self, link_scores): - "get softmaxed properly shaped links scores" - tmp = np.transpose(link_scores, (0, 2, 3, 1)) - tmp_reshaped = tmp.reshape(tmp.shape[:-1] + (8, 2)) - return self._softmax(tmp_reshaped, axis=-1)[0, :, :, :, 1] - - def _find_root(self, point): - root = point - update_parent = False - tmp = self.group_mask[root] - while tmp is not -1: - root = tmp - tmp = self.group_mask[root] - update_parent = True - if update_parent: - self.group_mask[point] = root - return root - - def _join(self, p1, p2): - root1 = self._find_root(p1) - root2 = self._find_root(p2) - if root1 != root2: - self.group_mask[root2] = root1 - - def _get_index(self, root): - if root not in self.root_map: - self.root_map[root] = len(self.root_map) + 1 - return self.root_map[root] - - def _get_all(self): - self.root_map = {} - self.mask = np.zeros_like(self.pixel_mask, dtype=np.int32) - - for point in self.points: - point_root = self._find_root(point) - bbox_idx = self._get_index(point_root) - self.mask[point] = bbox_idx - - def _get_neighbours_8(self, x, y): - w, h = self.w, self.h - tmp = [(0, x - 1, y - 1), (1, x, y - 1), - (2, x + 1, y - 1), (3, x - 1, y), - (4, x + 1, y), (5, x - 1, y + 1), - (6, x, y + 1), (7, x + 1, y + 1)] - - return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] - - def _get_neighbours_4(self, x, y): - w, h = self.w, self.h - tmp = [(1, x, y - 1), - (3, x - 1, y), - (4, x + 1, y), - (6, x, y + 1)] - - return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] - - def _mask_to_bboxes(self, min_area=300, min_height=10): - self.bboxes = [] - max_bbox_idx = self.mask.max() - mask_tmp = cv2.resize(self.mask, (self.image_width, self.image_height), interpolation=cv2.INTER_NEAREST) - - for bbox_idx in range(1, max_bbox_idx + 1): - bbox_mask = mask_tmp == bbox_idx - cnts, _ = cv2.findContours(bbox_mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) - if len(cnts) == 0: - continue - cnt = cnts[0] - rect, w, h = self._min_area_rect(cnt) - if min(w, h) < min_height: - continue - if w * h < min_area: - continue - self.bboxes.append(self._order_points(rect)) - - def _min_area_rect(self, cnt): - rect = cv2.minAreaRect(cnt) - w, h = rect[1] - box = cv2.boxPoints(rect) - box = np.int0(box) - return box, w, h - - def _order_points(self, rect): - """ (x, y) - Order: TL, TR, BR, BL - """ - tmp = np.zeros_like(rect) - sums = rect.sum(axis=1) - tmp[0] = rect[np.argmin(sums)] - tmp[2] = rect[np.argmax(sums)] - diff = np.diff(rect, axis=1) - tmp[1] = rect[np.argmin(diff)] - tmp[3] = rect[np.argmax(diff)] - return tmp - - def _decode(self): - for point in self.points: - y, x = point - neighbours = self._get_neighbours(x, y) - for n_idx, nx, ny in neighbours: - link_value = self.link_mask[y, x, n_idx] - pixel_cls = self.pixel_mask[ny, nx] - if link_value and pixel_cls: - self._join(point, (ny, nx)) - - self._get_all() - self._mask_to_bboxes() - - -label = 1 -pcd = PixelLinkDecoder() -for detection in detections: - frame = detection['frame_id'] - pcd.decode(detection['frame_height'], detection['frame_width'], detection['detections']) - for box in pcd.bboxes: - box = [[int(b[0]), int(b[1])] for b in box] - results.add_polygon(box, label, frame) diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json deleted file mode 100644 index f6a0aa87..00000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label_map": { - "1": "text" - } -} diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md deleted file mode 100644 index c7bac387..00000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Pixel Link - -* Model for the Detecting Scene Text vai Instance Segmentation -* Download using the `intel_model_zoo` using `$./downloader.py text-detection-0002` -* See [this Arxiv](https://arxiv.org/abs/1801.01315) link for the technical details diff --git a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py deleted file mode 100644 index 58a87fa3..00000000 --- a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py +++ /dev/null @@ -1,31 +0,0 @@ -import numpy as np -from skimage.measure import approximate_polygon, find_contours - -import cv2 - - -for frame_results in detections: - frame_height = frame_results['frame_height'] - frame_width = frame_results['frame_width'] - frame_number = frame_results['frame_id'] - detection = frame_results['detections'] - detection = detection[0, 0, :, :] - width, height = detection.shape - - for i in range(21): - zero = np.zeros((width,height),dtype=np.uint8) - - f = float(i) - zero = ((detection == f) * 255).astype(np.float32) - zero = cv2.resize(zero, dsize=(frame_width, frame_height), interpolation=cv2.INTER_CUBIC) - - contours = find_contours(zero, 0.8) - - for contour in contours: - contour = np.flip(contour, axis=1) - contour = approximate_polygon(contour, tolerance=2.5) - segmentation = contour.tolist() - if len(segmentation) < 3: - continue - - results.add_polygon(segmentation, i, frame_number) diff --git a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json deleted file mode 100644 index cbda289d..00000000 --- a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "label_map": { - "0": "road", - "1": "sidewalk", - "2": "building", - "3": "wall", - "4": "fence", - "5": "pole", - "6": "traffic light", - "7": "traffic sign", - "8": "vegetation", - "9": "terrain", - "10": "sky", - "11": "person", - "12": "rider", - "13": "car", - "14": "truck", - "15": "bus", - "16": "train", - "17": "motorcycle", - "18": "bicycle", - "19": "ego-vehicle", - "20": "background" - } -} diff --git a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/README.md b/utils/open_model_zoo/faster_rcnn_inception_v2_coco/README.md deleted file mode 100644 index a6ede445..00000000 --- a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Faster R-CNN with Inception v2 (https://arxiv.org/pdf/1801.04381.pdf) pre-trained on the COCO dataset - -### 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). diff --git a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/interp.py b/utils/open_model_zoo/faster_rcnn_inception_v2_coco/interp.py deleted file mode 100644 index f8a8a602..00000000 --- a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/interp.py +++ /dev/null @@ -1,19 +0,0 @@ -threshold = .5 - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - prediction = detection[0][0] - for obj in prediction: - obj_class = int(obj[1]) - obj_value = obj[2] - if obj_value >= threshold: - x = obj[3] * width - y = obj[4] * height - right = obj[5] * width - bottom = obj[6] * height - - results.add_box(x, y, right, bottom, obj_class, frame_number) diff --git a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/mapping.json b/utils/open_model_zoo/faster_rcnn_inception_v2_coco/mapping.json deleted file mode 100644 index 3efdb307..00000000 --- a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/mapping.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "label_map": { - "1": "person", - "2": "bicycle", - "3": "car", - "4": "motorcycle", - "5": "airplane", - "6": "bus", - "7": "train", - "8": "truck", - "9": "boat", - "10": "traffic_light", - "11": "fire_hydrant", - "13": "stop_sign", - "14": "parking_meter", - "15": "bench", - "16": "bird", - "17": "cat", - "18": "dog", - "19": "horse", - "20": "sheep", - "21": "cow", - "22": "elephant", - "23": "bear", - "24": "zebra", - "25": "giraffe", - "27": "backpack", - "28": "umbrella", - "31": "handbag", - "32": "tie", - "33": "suitcase", - "34": "frisbee", - "35": "skis", - "36": "snowboard", - "37": "sports_ball", - "38": "kite", - "39": "baseball_bat", - "40": "baseball_glove", - "41": "skateboard", - "42": "surfboard", - "43": "tennis_racket", - "44": "bottle", - "46": "wine_glass", - "47": "cup", - "48": "fork", - "49": "knife", - "50": "spoon", - "51": "bowl", - "52": "banana", - "53": "apple", - "54": "sandwich", - "55": "orange", - "56": "broccoli", - "57": "carrot", - "58": "hot_dog", - "59": "pizza", - "60": "donut", - "61": "cake", - "62": "chair", - "63": "couch", - "64": "potted_plant", - "65": "bed", - "67": "dining_table", - "70": "toilet", - "72": "tv", - "73": "laptop", - "74": "mouse", - "75": "remote", - "76": "keyboard", - "77": "cell_phone", - "78": "microwave", - "79": "oven", - "80": "toaster", - "81": "sink", - "83": "refrigerator", - "84": "book", - "85": "clock", - "86": "vase", - "87": "scissors", - "88": "teddy_bear", - "89": "hair_drier", - "90": "toothbrush" - } -} diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md deleted file mode 100644 index be7a8212..00000000 --- a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# mask_rcnn_inception_resnet_v2_atrous_coco - -## Use Case and High-Level Description - -Mask R-CNN Inception Resnet V2 Atrous is trained on COCO dataset and used for object instance segmentation. -For details, see a [paper](https://arxiv.org/pdf/1703.06870.pdf). - -## Specification - -| Metric | Value | -|---------------------------------|-------------------------------------------| -| Type | Instance segmentation | -| GFlops | 675.314 | -| MParams | 92.368 | -| Source framework | TensorFlow\* | - -## Legal Information - -[https://raw.githubusercontent.com/tensorflow/models/master/LICENSE]() - -## OpenVINO Conversion Notes - -In order to convert the code into the openvino format, please see the [following link](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_Object_Detection_API_Models.html#mask_r_cnn_topologies). - -The conversion command from the command line prompt will look something like the following. - -```shell -$ python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py \ - --input_model /path/to/frozen_inference_graph.pb \ - --tensorflow_use_custom_operations_config /opt/intel/openvino/deployment_tools/model_optimizer/extensions/front/tf/mask_rcnn_support.json \ - --tensorflow_object_detection_api_pipeline_config /path/to/pipeline.config -``` diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py deleted file mode 100644 index 6625a834..00000000 --- a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py +++ /dev/null @@ -1,64 +0,0 @@ -import numpy as np -import cv2 -from skimage.measure import approximate_polygon, find_contours - - -MASK_THRESHOLD = .5 -PROBABILITY_THRESHOLD = 0.2 - - -# Ref: https://software.intel.com/en-us/forums/computer-vision/topic/804895 -def segm_postprocess(box: list, raw_cls_mask, im_h, im_w, threshold): - ymin, xmin, ymax, xmax = box - - width = int(abs(xmax - xmin)) - height = int(abs(ymax - ymin)) - - result = np.zeros((im_h, im_w), dtype=np.uint8) - resized_mask = cv2.resize(raw_cls_mask, dsize=(height, width), interpolation=cv2.INTER_CUBIC) - - # extract the ROI of the image - ymin = int(round(ymin)) - xmin = int(round(xmin)) - ymax = ymin + height - xmax = xmin + width - result[xmin:xmax, ymin:ymax] = (resized_mask>threshold).astype(np.uint8) * 255 - - return result - - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - masks = detection['masks'] - boxes = detection['reshape_do_2d'] - - for index, box in enumerate(boxes): - label = int(box[1]) - obj_value = box[2] - if obj_value >= PROBABILITY_THRESHOLD: - x = box[3] * width - y = box[4] * height - right = box[5] * width - bottom = box[6] * height - mask = masks[index][label - 1] - - mask = segm_postprocess((x, y, right, bottom), - mask, - height, - width, - MASK_THRESHOLD) - - contours = find_contours(mask, MASK_THRESHOLD) - contour = contours[0] - contour = np.flip(contour, axis=1) - contour = approximate_polygon(contour, tolerance=2.5) - segmentation = contour.tolist() - - - # NOTE: if you want to see the boxes, uncomment next line - # results.add_box(x, y, right, bottom, label, frame_number) - results.add_polygon(segmentation, label, frame_number) diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json deleted file mode 100644 index 3efdb307..00000000 --- a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "label_map": { - "1": "person", - "2": "bicycle", - "3": "car", - "4": "motorcycle", - "5": "airplane", - "6": "bus", - "7": "train", - "8": "truck", - "9": "boat", - "10": "traffic_light", - "11": "fire_hydrant", - "13": "stop_sign", - "14": "parking_meter", - "15": "bench", - "16": "bird", - "17": "cat", - "18": "dog", - "19": "horse", - "20": "sheep", - "21": "cow", - "22": "elephant", - "23": "bear", - "24": "zebra", - "25": "giraffe", - "27": "backpack", - "28": "umbrella", - "31": "handbag", - "32": "tie", - "33": "suitcase", - "34": "frisbee", - "35": "skis", - "36": "snowboard", - "37": "sports_ball", - "38": "kite", - "39": "baseball_bat", - "40": "baseball_glove", - "41": "skateboard", - "42": "surfboard", - "43": "tennis_racket", - "44": "bottle", - "46": "wine_glass", - "47": "cup", - "48": "fork", - "49": "knife", - "50": "spoon", - "51": "bowl", - "52": "banana", - "53": "apple", - "54": "sandwich", - "55": "orange", - "56": "broccoli", - "57": "carrot", - "58": "hot_dog", - "59": "pizza", - "60": "donut", - "61": "cake", - "62": "chair", - "63": "couch", - "64": "potted_plant", - "65": "bed", - "67": "dining_table", - "70": "toilet", - "72": "tv", - "73": "laptop", - "74": "mouse", - "75": "remote", - "76": "keyboard", - "77": "cell_phone", - "78": "microwave", - "79": "oven", - "80": "toaster", - "81": "sink", - "83": "refrigerator", - "84": "book", - "85": "clock", - "86": "vase", - "87": "scissors", - "88": "teddy_bear", - "89": "hair_drier", - "90": "toothbrush" - } -} diff --git a/utils/open_model_zoo/yolov3/README.md b/utils/open_model_zoo/yolov3/README.md deleted file mode 100644 index 2e47953c..00000000 --- a/utils/open_model_zoo/yolov3/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Object Detection YOLO V3 Python Demo, Async API Performance Showcase - -See [these instructions][1] for converting the yolo weights to the OpenVino format. - -As of OpenVINO 2019 R3, only tensorflow 1.13 and NetworkX 2.3. -These can be explicitly installed using the following command. - -```bash -python3 -m pip install tensorflow==1.13 networkx==2.3 -``` - - -Additionally, at the time of writing, the model optimizer required an input shape. - -``` bash -python3 mo_tf.py \ - --input_model /path/to/yolo_v3.pb \ - --tensorflow_use_custom_operations_config $MO_ROOT/extensions/front/tf/yolo_v3.json \ - --input_shape [1,416,416,3] -``` - -[1]: https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_YOLO_From_Tensorflow.html diff --git a/utils/open_model_zoo/yolov3/interp.py b/utils/open_model_zoo/yolov3/interp.py deleted file mode 100644 index 4c76c854..00000000 --- a/utils/open_model_zoo/yolov3/interp.py +++ /dev/null @@ -1,160 +0,0 @@ -from math import exp - - -class Parser: - IOU_THRESHOLD = 0.4 - PROB_THRESHOLD = 0.5 - - def __init__(self): - self.objects = [] - - def scale_bbox(self, x, y, h, w, class_id, confidence, h_scale, w_scale): - xmin = int((x - w / 2) * w_scale) - ymin = int((y - h / 2) * h_scale) - xmax = int(xmin + w * w_scale) - ymax = int(ymin + h * h_scale) - - return dict(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, class_id=class_id, confidence=confidence) - - def entry_index(self, side, coord, classes, location, entry): - side_power_2 = side ** 2 - n = location // side_power_2 - loc = location % side_power_2 - return int(side_power_2 * (n * (coord + classes + 1) + entry) + loc) - - def intersection_over_union(self, box_1, box_2): - width_of_overlap_area = min(box_1['xmax'], box_2['xmax']) - max(box_1['xmin'], box_2['xmin']) - height_of_overlap_area = min(box_1['ymax'], box_2['ymax']) - max(box_1['ymin'], box_2['ymin']) - if width_of_overlap_area < 0 or height_of_overlap_area < 0: - area_of_overlap = 0 - else: - area_of_overlap = width_of_overlap_area * height_of_overlap_area - box_1_area = (box_1['ymax'] - box_1['ymin']) * (box_1['xmax'] - box_1['xmin']) - box_2_area = (box_2['ymax'] - box_2['ymin']) * (box_2['xmax'] - box_2['xmin']) - area_of_union = box_1_area + box_2_area - area_of_overlap - if area_of_union == 0: - return 0 - return area_of_overlap / area_of_union - - - def sort_objects(self): - self.objects = sorted(self.objects, key=lambda obj : obj['confidence'], reverse=True) - - for i in range(len(self.objects)): - if self.objects[i]['confidence'] == 0: - continue - for j in range(i + 1, len(self.objects)): - if self.intersection_over_union(self.objects[i], self.objects[j]) > self.IOU_THRESHOLD: - self.objects[j]['confidence'] = 0 - - def parse_yolo_region(self, blob: 'np.ndarray', original_shape: list, params: dict) -> list: - - # YOLO magic numbers - # See: https://github.com/opencv/open_model_zoo/blob/acf297c73db8cb3f68791ae1fad4a7cc4a6039e5/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L61 - num = 3 - coords = 4 - classes = 80 - # ----------------- - - _, _, out_blob_h, out_blob_w = blob.shape - assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ - "be equal to width. Current height = {}, current width = {}" \ - "".format(out_blob_h, out_blob_w) - - # ------ Extracting layer parameters -- - orig_im_h, orig_im_w = original_shape - predictions = blob.flatten() - side_square = params['side'] * params['side'] - - # ------ Parsing YOLO Region output -- - for i in range(side_square): - row = i // params['side'] - col = i % params['side'] - for n in range(num): - # -----entry index calcs------ - obj_index = self.entry_index(params['side'], coords, classes, n * side_square + i, coords) - scale = predictions[obj_index] - if scale < self.PROB_THRESHOLD: - continue - box_index = self.entry_index(params['side'], coords, classes, n * side_square + i, 0) - - # Network produces location predictions in absolute coordinates of feature maps. - # Scale it to relative coordinates. - x = (col + predictions[box_index + 0 * side_square]) / params['side'] * 416 - y = (row + predictions[box_index + 1 * side_square]) / params['side'] * 416 - # Value for exp is very big number in some cases so following construction is using here - try: - h_exp = exp(predictions[box_index + 3 * side_square]) - w_exp = exp(predictions[box_index + 2 * side_square]) - except OverflowError: - continue - - w = w_exp * params['anchors'][2 * n] - h = h_exp * params['anchors'][2 * n + 1] - - for j in range(classes): - class_index = self.entry_index(params['side'], coords, classes, n * side_square + i, - coords + 1 + j) - confidence = scale * predictions[class_index] - if confidence < self.PROB_THRESHOLD: - continue - - self.objects.append(self.scale_bbox(x=x, - y=y, - h=h, - w=w, - class_id=j, - confidence=confidence, - h_scale=(orig_im_h/416), - w_scale=(orig_im_w/416))) - - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - original_shape = (height, width) - - # https://github.com/opencv/open_model_zoo/blob/master/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L72 - anchors = [10,13,16,30,33,23,30,61,62,45,59,119,116,90,156,198,373,326] - conv_6 = {'side': 13, 'mask': [6,7,8]} - conv_14 = {'side': 26, 'mask': [3,4,5]} - conv_22 = {'side': 52, 'mask': [0,1,2]} - - yolo_params = {'detector/yolo-v3/Conv_6/BiasAdd/YoloRegion': conv_6, - 'detector/yolo-v3/Conv_14/BiasAdd/YoloRegion': conv_14, - 'detector/yolo-v3/Conv_22/BiasAdd/YoloRegion': conv_22} - - for conv_net in yolo_params.values(): - mask = conv_net['mask'] - masked_anchors = [] - for idx in mask: - masked_anchors += [anchors[idx * 2], anchors[idx * 2 + 1]] - - conv_net['anchors'] = masked_anchors - - parser = Parser() - - for name, blob in detection.items(): - parser.parse_yolo_region(blob, original_shape, yolo_params[name]) - - parser.sort_objects() - - objects = [] - for obj in parser.objects: - if obj['confidence'] >= parser.PROB_THRESHOLD: - label = obj['class_id'] - xmin = obj['xmin'] - xmax = obj['xmax'] - ymin = obj['ymin'] - ymax = obj['ymax'] - - # Enforcing extra checks for bounding box coordinates - xmin = max(0,xmin) - ymin = max(0,ymin) - xmax = min(xmax,width) - ymax = min(ymax,height) - - results.add_box(xmin, ymin, xmax, ymax, label, frame_number) diff --git a/utils/open_model_zoo/yolov3/mapping.json b/utils/open_model_zoo/yolov3/mapping.json deleted file mode 100644 index bfb65a24..00000000 --- a/utils/open_model_zoo/yolov3/mapping.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "label_map": { - "0": "person", - "1": "bicycle", - "2": "car", - "3": "motorbike", - "4": "aeroplane", - "5": "bus", - "6": "train", - "7": "truck", - "8": "boat", - "9": "traffic light", - "10": "fire hydrant", - "11": "stop sign", - "12": "parking meter", - "13": "bench", - "14": "bird", - "15": "cat", - "16": "dog", - "17": "horse", - "18": "sheep", - "19": "cow", - "20": "elephant", - "21": "bear", - "22": "zebra", - "23": "giraffe", - "24": "backpack", - "25": "umbrella", - "26": "handbag", - "27": "tie", - "28": "suitcase", - "29": "frisbee", - "30": "skis", - "31": "snowboard", - "32": "sports ball", - "33": "kite", - "34": "baseball bat", - "35": "baseball glove", - "36": "skateboard", - "37": "surfboard", - "38": "tennis racket", - "39": "bottle", - "40": "wine glass", - "41": "cup", - "42": "fork", - "43": "knife", - "44": "spoon", - "45": "bowl", - "46": "banana", - "47": "apple", - "48": "sandwich", - "49": "orange", - "50": "broccoli", - "51": "carrot", - "52": "hot dog", - "53": "pizza", - "54": "donut", - "55": "cake", - "56": "chair", - "57": "sofa", - "58": "pottedplant", - "59": "bed", - "60": "diningtable", - "61": "toilet", - "62": "tvmonitor", - "63": "laptop", - "64": "mouse", - "65": "remote", - "66": "keyboard", - "67": "cell phone", - "68": "microwave", - "69": "oven", - "70": "toaster", - "71": "sink", - "72": "refrigerator", - "73": "book", - "74": "clock", - "75": "vase", - "76": "scissors", - "77": "teddy bear", - "78": "hair drier", - "79": "toothbrush" - } -}