diff --git a/CHANGELOG.md b/CHANGELOG.md index eb9c68ec..79affefd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Django templates for email and user guide () +- Saving relative paths in dummy chunks instead of absolute () +- Objects with a specific label cannot be displayed if at least one tag with the label exist () +- Wrong attribute can be removed in labels editor () +- UI fails with the error "Cannot read property 'label' of undefined" () +- Exception: "Value must be a user instance" () +- Reset zoom option doesn't work in tag annotation mode () +- Canvas is busy error () ### Security diff --git a/Dockerfile.ui b/Dockerfile.ui index a12b60d8..25a2099e 100644 --- a/Dockerfile.ui +++ b/Dockerfile.ui @@ -21,8 +21,6 @@ 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 ci diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json index 52e1e138..1d6edfb7 100644 --- a/cvat-core/package-lock.json +++ b/cvat-core/package-lock.json @@ -1867,9 +1867,9 @@ "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" }, "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", "requires": { "follow-redirects": "^1.10.0" } diff --git a/cvat-core/package.json b/cvat-core/package.json index 84140dde..2319fa1c 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -33,7 +33,7 @@ "webpack-cli": "^3.3.2" }, "dependencies": { - "axios": "^0.20.0", + "axios": "^0.21.0", "browser-or-node": "^1.2.1", "cvat-data": "../cvat-data", "detect-browser": "^5.2.0", diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index c8a305a6..4fa35bc9 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.10.0", + "version": "1.10.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1213,9 +1213,9 @@ "dev": true }, "@types/react": { - "version": "16.9.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.53.tgz", - "integrity": "sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==", + "version": "16.9.54", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.54.tgz", + "integrity": "sha512-GhawhYraQZpGFO2hVMArjPrYbnA/6+DS8SubK8IPhhVClmKqANihsRenOm5E0mvqK0m/BKoqVktA1O1+Xvlz9w==", "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1231,9 +1231,9 @@ } }, "@types/react-dom": { - "version": "16.9.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", - "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", + "version": "16.9.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.9.tgz", + "integrity": "sha512-jE16FNWO3Logq/Lf+yvEAjKzhpST/Eac8EMd1i4dgZdMczfgqC8EjpxwNgEe3SExHYLliabXDh9DEhhqnlXJhg==", "requires": { "@types/react": "*" } @@ -1854,9 +1854,9 @@ } }, "antd": { - "version": "3.26.18", - "resolved": "https://registry.npmjs.org/antd/-/antd-3.26.18.tgz", - "integrity": "sha512-TPuacNJJNPji+LnapU46uWGqi+6JlyH75paMNs95IH0F7gGYtp4oSkua88gGsoAaUbDxTIF+cWI9mdIsr7ywlw==", + "version": "3.26.20", + "resolved": "https://registry.npmjs.org/antd/-/antd-3.26.20.tgz", + "integrity": "sha512-VIous4ofZfxFtd9K1h9MpRX2sDDpj3QcOFi3YgIc9B/uyDli/GlLb8SWKfQfJaMkaxwatIv503dag2Tog+hiEg==", "requires": { "@ant-design/create-react-context": "^0.2.4", "@ant-design/icons": "~2.1.1", @@ -3779,11 +3779,10 @@ } }, "create-react-class": { - "version": "15.6.3", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", - "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", "requires": { - "fbjs": "^0.8.9", "loose-envify": "^1.3.1", "object-assign": "^4.1.1" } @@ -3957,9 +3956,9 @@ } }, "csstype": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz", - "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==" }, "currently-unhandled": { "version": "0.4.1", @@ -17052,7 +17051,7 @@ }, "fs-minipass": { "version": "1.2.7", - "resolved": "", + "resolved": false, "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "optional": true, "requires": { @@ -17067,7 +17066,7 @@ }, "gauge": { "version": "2.7.4", - "resolved": "", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { @@ -17083,7 +17082,7 @@ }, "glob": { "version": "7.1.6", - "resolved": "", + "resolved": false, "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "optional": true, "requires": { @@ -17121,7 +17120,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { @@ -17173,7 +17172,7 @@ }, "minipass": { "version": "2.9.0", - "resolved": "", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "optional": true, "requires": { @@ -17183,7 +17182,7 @@ }, "minizlib": { "version": "1.3.3", - "resolved": "", + "resolved": false, "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "optional": true, "requires": { @@ -17218,7 +17217,7 @@ }, "node-pre-gyp": { "version": "0.14.0", - "resolved": "", + "resolved": false, "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", "optional": true, "requires": { @@ -17272,7 +17271,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { @@ -17296,7 +17295,7 @@ }, "once": { "version": "1.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { @@ -17366,7 +17365,7 @@ }, "rimraf": { "version": "2.7.1", - "resolved": "", + "resolved": false, "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "optional": true, "requires": { @@ -17446,7 +17445,7 @@ }, "tar": { "version": "4.4.13", - "resolved": "", + "resolved": false, "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "optional": true, "requires": { @@ -17476,13 +17475,13 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "optional": true } @@ -28684,9 +28683,9 @@ } }, "rc-switch": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-1.9.0.tgz", - "integrity": "sha512-Isas+egaK6qSk64jaEw4GgPStY4umYDbT7ZY93bZF1Af+b/JEsKsJdNOU2qG3WI0Z6tXo2DDq0kJCv8Yhu0zww==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-1.9.2.tgz", + "integrity": "sha512-qaK7mY4FLDKy99Hq3A1tf8CcqfzKtHp9LPX8WTnZ0MzdHCTneSARb1XD7Eqeu8BactasYGsi2bF9p18Q+/5JEw==", "requires": { "classnames": "^2.2.1", "prop-types": "^15.5.6", @@ -28928,15 +28927,43 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-redux": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz", - "integrity": "sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz", + "integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==", "requires": { - "@babel/runtime": "^7.5.5", - "hoist-non-react-statics": "^3.3.0", + "@babel/runtime": "^7.12.1", + "hoist-non-react-statics": "^3.3.2", "loose-envify": "^1.4.0", "prop-types": "^15.7.2", - "react-is": "^16.9.0" + "react-is": "^16.13.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.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==" + }, + "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==" + } } }, "react-router": { @@ -32254,9 +32281,9 @@ "dev": true }, "whatwg-fetch": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz", - "integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz", + "integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==" }, "which": { "version": "1.3.1", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 1afed2a4..7ea9c449 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.10.0", + "version": "1.10.6", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { @@ -49,15 +49,15 @@ "dependencies": { "@types/lodash": "^4.14.165", "@types/platform": "^1.3.3", - "@types/react": "^16.9.53", + "@types/react": "^16.9.54", "@types/react-color": "^3.0.4", - "@types/react-dom": "^16.9.0", + "@types/react-dom": "^16.9.9", "@types/react-redux": "^7.1.2", "@types/react-router": "^5.0.5", "@types/react-router-dom": "^5.1.6", "@types/react-share": "^3.0.3", "@types/redux-logger": "^3.0.8", - "antd": "^3.26.18", + "antd": "^3.26.20", "copy-to-clipboard": "^3.3.1", "cvat-canvas": "file:../cvat-canvas", "cvat-core": "file:../cvat-core", @@ -72,7 +72,7 @@ "react-cookie": "^4.0.3", "react-dom": "^16.14.0", "react-hotkeys": "^2.0.0", - "react-redux": "^7.1.1", + "react-redux": "^7.2.2", "react-router": "^5.1.0", "react-router-dom": "^5.1.0", "react-share": "^3.0.1", 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 d760411a..acc697ae 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 @@ -10,7 +10,9 @@ import Icon from 'antd/lib/icon'; import Layout from 'antd/lib/layout/layout'; import Slider, { SliderValue } from 'antd/lib/slider'; -import { ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType } from 'reducers/interfaces'; +import { + ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType, +} from 'reducers/interfaces'; import { LogType } from 'cvat-logger'; import { Canvas } from 'cvat-canvas-wrapper'; import getCore from 'cvat-core-wrapper'; @@ -217,10 +219,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateCanvas(); } - if ( - prevProps.frame !== frameData.number && - ((resetZoom && workspace !== Workspace.ATTRIBUTE_ANNOTATION) || workspace === Workspace.TAG_ANNOTATION) - ) { + if (prevProps.frame !== frameData.number && resetZoom && workspace !== Workspace.ATTRIBUTE_ANNOTATION) { canvasInstance.html().addEventListener( 'canvas.setup', () => { @@ -304,7 +303,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { } private onCanvasShapeDrawn = (event: any): void => { - const { jobInstance, activeLabelID, activeObjectType, frame, onShapeDrawn, onCreateAnnotations } = this.props; + const { + jobInstance, activeLabelID, activeObjectType, frame, onShapeDrawn, onCreateAnnotations, + } = this.props; if (!event.detail.continue) { onShapeDrawn(); @@ -327,7 +328,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasObjectsMerged = (event: any): void => { - const { jobInstance, frame, onMergeAnnotations, onMergeObjects } = this.props; + const { + jobInstance, frame, onMergeAnnotations, onMergeObjects, + } = this.props; onMergeObjects(false); @@ -340,7 +343,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasObjectsGroupped = (event: any): void => { - const { jobInstance, frame, onGroupAnnotations, onGroupObjects } = this.props; + const { + jobInstance, frame, onGroupAnnotations, onGroupObjects, + } = this.props; onGroupObjects(false); @@ -349,7 +354,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasTrackSplitted = (event: any): void => { - const { jobInstance, frame, onSplitAnnotations, onSplitTrack } = this.props; + const { + jobInstance, frame, onSplitAnnotations, onSplitTrack, + } = this.props; onSplitTrack(false); @@ -429,7 +436,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasCursorMoved = async (event: any): Promise => { - const { jobInstance, activatedStateID, workspace, onActivateObject } = this.props; + const { + jobInstance, activatedStateID, workspace, onActivateObject, + } = this.props; if (workspace !== Workspace.STANDARD) { return; @@ -560,7 +569,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { } private updateShapesView(): void { - const { annotations, opacity, colorBy, outlined, outlineColor } = this.props; + const { + annotations, opacity, colorBy, outlined, outlineColor, + } = this.props; for (const state of annotations) { let shapeColor = ''; @@ -588,7 +599,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { } private updateCanvas(): void { - const { curZLayer, annotations, frameData, canvasInstance } = this.props; + const { + curZLayer, annotations, frameData, canvasInstance, + } = this.props; if (frameData !== null) { canvasInstance.setup( diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx index 2903e448..db1bb4a8 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx @@ -109,7 +109,7 @@ function PlayerButtons(props: Props): JSX.Element { - } + )} > - } + )} > {nextButton} diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index b292f41f..3e1a4f58 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -18,7 +18,9 @@ import ColorPicker from 'components/annotation-page/standard-workspace/objects-s import { ColorizeIcon } from 'icons'; import patterns from 'utils/validation-patterns'; import consts from 'consts'; -import { equalArrayHead, idGenerator, Label, Attribute } from './common'; +import { + equalArrayHead, idGenerator, Label, Attribute, +} from './common'; export enum AttributeType { SELECT = 'SELECT', @@ -318,9 +320,9 @@ class LabelForm extends React.PureComponent { ); } - private renderAttribute = (key: number, index: number): JSX.Element => { + private renderAttribute = (key: number): JSX.Element => { const { label, form } = this.props; - const attr = label && index < label.attributes.length ? label.attributes[index] : null; + const attr = label ? label.attributes.filter((_attr: any): boolean => _attr.id === key)[0] : null; return ( diff --git a/cvat-ui/src/components/task-page/user-selector.tsx b/cvat-ui/src/components/task-page/user-selector.tsx index 0cc42db9..f5fdcf0b 100644 --- a/cvat-ui/src/components/task-page/user-selector.tsx +++ b/cvat-ui/src/components/task-page/user-selector.tsx @@ -83,13 +83,7 @@ export default function UserSelector(props: Props): JSX.Element { if (value && !users.filter((user) => user.id === value.id).length) { core.users.get({ id: value.id }).then((result: User[]) => { const [user] = result; - setUsers([ - ...users, - { - id: user.id, - username: user.username, - }, - ]); + setUsers([...users, user]); setSearchPhrase(user.username); }); } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx index 43b2ad4a..c172abb5 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-context-menu.tsx @@ -24,7 +24,10 @@ function mapStateToProps(state: CombinedState): StateToProps { annotation: { annotations: { activatedStateID, collapsed, states: objectStates }, canvas: { - contextMenu: { visible, top, left, type }, + contextMenu: { + visible, top, left, type, + }, + ready, }, }, } = state; @@ -33,7 +36,11 @@ function mapStateToProps(state: CombinedState): StateToProps { activatedStateID, collapsed: activatedStateID !== null ? collapsed[activatedStateID] : undefined, objectStates, - visible, + visible: + activatedStateID !== null && + visible && + ready && + objectStates.map((_state: any): number => _state.clientID).includes(activatedStateID), left, top, type, @@ -166,7 +173,9 @@ class CanvasContextMenuContainer extends React.PureComponent { public render(): JSX.Element { const { left, top } = this.state; - const { visible, activatedStateID, objectStates, type } = this.props; + const { + visible, activatedStateID, objectStates, type, + } = this.props; return ( <> diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx index 8c74f714..8e89e244 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { updateAnnotationsAsync } from 'actions/annotation-actions'; import LabelItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/label-item'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState, ObjectType } from 'reducers/interfaces'; interface OwnProps { labelID: number; @@ -92,8 +92,8 @@ class LabelItemContainer extends React.PureComponent { let statesLocked = true; ownObjectStates.forEach((objectState: any) => { - const { lock } = objectState; - if (!lock) { + const { lock, objectType } = objectState; + if (!lock && objectType !== ObjectType.TAG) { statesHidden = statesHidden && objectState.hidden; statesLocked = statesLocked && objectState.lock; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 2d2e6f51..987598f7 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -7,7 +7,9 @@ import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; import { LogType } from 'cvat-logger'; -import { ActiveControl, CombinedState, ColorBy, ShapeType } from 'reducers/interfaces'; +import { + ActiveControl, CombinedState, ColorBy, ShapeType, +} from 'reducers/interfaces'; import { collapseObjectItems, updateAnnotationsAsync, @@ -83,40 +85,25 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { const stateIDs = states.map((_state: any): number => _state.clientID); const index = stateIDs.indexOf(clientID); - try { - const collapsedState = - typeof statesCollapsed[clientID] === 'undefined' ? initialCollapsed : statesCollapsed[clientID]; - - return { - objectState: states[index], - collapsed: collapsedState, - attributes: jobAttributes[states[index].label.id], - labels, - ready, - activeControl, - colorBy, - jobInstance, - frameNumber, - activated: activatedStateID === clientID, - minZLayer, - maxZLayer, - normalizedKeyMap, - aiToolsRef, - }; - } catch (exception) { - // we have an exception here sometimes - // but I cannot understand when it happens and what is the root reason - // maybe this temporary hack helps us - const dataObject = { - index, - frameNumber, - clientID: own.clientID, - stateIDs, - }; - throw new Error( - `${exception.toString()} in mapStateToProps of ObjectItemContainer. Data are ${JSON.stringify(dataObject)}`, - ); - } + const collapsedState = + typeof statesCollapsed[clientID] === 'undefined' ? initialCollapsed : statesCollapsed[clientID]; + + return { + objectState: states[index], + collapsed: collapsedState, + attributes: jobAttributes[states[index].label.id], + labels, + ready, + activeControl, + colorBy, + jobInstance, + frameNumber, + activated: activatedStateID === clientID, + minZLayer, + maxZLayer, + normalizedKeyMap, + aiToolsRef, + }; } function mapDispatchToProps(dispatch: any): DispatchToProps { @@ -219,7 +206,9 @@ class ObjectItemContainer extends React.PureComponent { }; private activate = (): void => { - const { activateObject, objectState, ready, activeControl } = this.props; + const { + activateObject, objectState, ready, activeControl, + } = this.props; if (ready && activeControl === ActiveControl.CURSOR) { activateObject(objectState.clientID); @@ -324,7 +313,9 @@ class ObjectItemContainer extends React.PureComponent { } public render(): JSX.Element { - const { objectState, collapsed, labels, attributes, activated, colorBy, normalizedKeyMap } = this.props; + const { + objectState, collapsed, labels, attributes, activated, colorBy, normalizedKeyMap, + } = this.props; let stateColor = ''; if (colorBy === ColorBy.INSTANCE) { diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 42fdf1db..5a7a40fc 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -231,7 +231,9 @@ class AnnotationTopBarContainer extends React.PureComponent { } private undo = (): void => { - const { undo, jobInstance, frameNumber, canvasInstance } = this.props; + const { + undo, jobInstance, frameNumber, canvasInstance, + } = this.props; if (canvasInstance.isAbleToChangeFrame()) { undo(jobInstance, frameNumber); @@ -239,7 +241,9 @@ class AnnotationTopBarContainer extends React.PureComponent { }; private redo = (): void => { - const { redo, jobInstance, frameNumber, canvasInstance } = this.props; + const { + redo, jobInstance, frameNumber, canvasInstance, + } = this.props; if (canvasInstance.isAbleToChangeFrame()) { redo(jobInstance, frameNumber); @@ -253,7 +257,9 @@ class AnnotationTopBarContainer extends React.PureComponent { }; private onSwitchPlay = (): void => { - const { frameNumber, jobInstance, onSwitchPlay, playing } = this.props; + const { + frameNumber, jobInstance, onSwitchPlay, playing, + } = this.props; if (playing) { onSwitchPlay(false); @@ -263,7 +269,9 @@ class AnnotationTopBarContainer extends React.PureComponent { }; private onFirstFrame = (): void => { - const { frameNumber, jobInstance, playing, onSwitchPlay } = this.props; + const { + frameNumber, jobInstance, playing, onSwitchPlay, + } = this.props; const newFrame = jobInstance.startFrame; if (newFrame !== frameNumber) { @@ -275,7 +283,9 @@ class AnnotationTopBarContainer extends React.PureComponent { }; private onBackward = (): void => { - const { frameNumber, frameStep, jobInstance, playing, onSwitchPlay } = this.props; + const { + frameNumber, frameStep, jobInstance, playing, onSwitchPlay, + } = this.props; const newFrame = Math.max(jobInstance.startFrame, frameNumber - frameStep); if (newFrame !== frameNumber) { @@ -288,7 +298,9 @@ class AnnotationTopBarContainer extends React.PureComponent { private onPrevFrame = (): void => { const { prevButtonType } = this.state; - const { frameNumber, jobInstance, playing, onSwitchPlay, searchAnnotations, searchEmptyFrame } = this.props; + const { + frameNumber, jobInstance, playing, onSwitchPlay, + } = this.props; const { startFrame } = jobInstance; const newFrame = Math.max(jobInstance.startFrame, frameNumber - 1); @@ -296,19 +308,22 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } + if (prevButtonType === 'regular') { this.changeFrame(newFrame); } else if (prevButtonType === 'filtered') { - searchAnnotations(jobInstance, frameNumber - 1, startFrame); + this.searchAnnotations(frameNumber - 1, startFrame); } else { - searchEmptyFrame(jobInstance, frameNumber - 1, startFrame); + this.searchEmptyFrame(frameNumber - 1, startFrame); } } }; private onNextFrame = (): void => { const { nextButtonType } = this.state; - const { frameNumber, jobInstance, playing, onSwitchPlay, searchAnnotations, searchEmptyFrame } = this.props; + const { + frameNumber, jobInstance, playing, onSwitchPlay, + } = this.props; const { stopFrame } = jobInstance; const newFrame = Math.min(jobInstance.stopFrame, frameNumber + 1); @@ -316,18 +331,21 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } + if (nextButtonType === 'regular') { this.changeFrame(newFrame); } else if (nextButtonType === 'filtered') { - searchAnnotations(jobInstance, frameNumber + 1, stopFrame); + this.searchAnnotations(frameNumber + 1, stopFrame); } else { - searchEmptyFrame(jobInstance, frameNumber + 1, stopFrame); + this.searchEmptyFrame(frameNumber + 1, stopFrame); } } }; private onForward = (): void => { - const { frameNumber, frameStep, jobInstance, playing, onSwitchPlay } = this.props; + const { + frameNumber, frameStep, jobInstance, playing, onSwitchPlay, + } = this.props; const newFrame = Math.min(jobInstance.stopFrame, frameNumber + frameStep); if (newFrame !== frameNumber) { @@ -339,7 +357,9 @@ class AnnotationTopBarContainer extends React.PureComponent { }; private onLastFrame = (): void => { - const { frameNumber, jobInstance, playing, onSwitchPlay } = this.props; + const { + frameNumber, jobInstance, playing, onSwitchPlay, + } = this.props; const newFrame = jobInstance.stopFrame; if (newFrame !== frameNumber) { @@ -418,6 +438,20 @@ class AnnotationTopBarContainer extends React.PureComponent { } } + private searchAnnotations(start: number, stop: number): void { + const { canvasInstance, jobInstance, searchAnnotations } = this.props; + if (canvasInstance.isAbleToChangeFrame()) { + searchAnnotations(jobInstance, start, stop); + } + } + + private searchEmptyFrame(start: number, stop: number): void { + const { canvasInstance, jobInstance, searchAnnotations } = this.props; + if (canvasInstance.isAbleToChangeFrame()) { + searchAnnotations(jobInstance, start, stop); + } + } + public render(): JSX.Element { const { nextButtonType, prevButtonType } = this.state; const { diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index e724d242..ca3a88d4 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -354,7 +354,7 @@ def _create_thread(tid, data): img_sizes = [] with open(db_data.get_dummy_chunk_path(chunk_number), 'w') as dummy_chunk: for path, frame_id in chunk_paths: - dummy_chunk.write(path + '\n') + dummy_chunk.write(os.path.relpath(path, upload_dir) + '\n') img_sizes.append(extractor.get_image_size(frame_id)) db_images.extend([ diff --git a/docs/rest_api_design.md b/docs/rest_api_design.md new file mode 100644 index 00000000..1bf85454 --- /dev/null +++ b/docs/rest_api_design.md @@ -0,0 +1,37 @@ +# REST API design principles + +## REST API scheme + +Common scheme for our REST API is ` [namespace] `. +- `VERB` can be `POST`, `GET`, `PATCH`, `PUT`, `DELETE`. +- `namespace` should scope some specific functionality like `auth`, `lambda`. + It is optional in the scheme. +- Typical `objects` are `tasks`, `projects`, `jobs`. +- When you want to extract a specific object from a collection, just specify its `id`. +- An `action` can be used to simplify REST API or provide an endpoint for entities + without `objects` endpoint like `annotations`, `data`, `data/meta`. Note: action + should not duplicate other endpoints without a reason. + +## Design principles +- Use nouns instead of verbs in endpoint paths. For example, +`POST /api/v1/tasks` instead of `POST /api/v1/tasks/create`. +- Accept and respond with JSON whenever it is possible +- Name collections with plural nouns (e.g. `/tasks`, `/projects`) +- Try to keep the API structure flat. Prefer two separate endpoints +for `/projects` and `/tasks` instead of `/projects/:id1/tasks/:id2`. Use +filters to extract necessary information like `/tasks/:id2?project=:id1`. +In some cases it is useful to get all `tasks`. If the structure is +hierarchical, it cannot be done easily. Also you have to know both `:id1` +and `:id2` to get information about the task. +_Note: for now we accept `GET /tasks/:id2/jobs` but it should be replaced +by `/jobs?task=:id2` in the future_. +- Handle errors gracefully and return standard error codes (e.g. `201`, `400`) +- Allow filtering, sorting, and pagination +- Maintain good security practices +- Cache data to improve performance +- Versioning our APIs (e.g. `/api/v1`, `/api/v2`). It should be done when you +delete an endpoint or modify its behaviors. + +## Links +- [Best practices for REST API design](https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/) +- [Flat vs. nested resources](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) diff --git a/serverless/openvino/dextr/nuclio/function.yaml b/serverless/openvino/dextr/nuclio/function.yaml index 47106242..6faa753e 100644 --- a/serverless/openvino/dextr/nuclio/function.yaml +++ b/serverless/openvino/dextr/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: openvino.dextr + name: openvino-dextr namespace: cvat annotations: name: DEXTR 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 index 44075bbf..ab8845e4 100644 --- a/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/function.yaml +++ b/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: openvino.omz.intel.person-reidentification-retail-0300 + name: openvino-omz-intel-person-reidentification-retail-0300 namespace: cvat annotations: name: Person reidentification 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 index f78a9258..b871a01c 100644 --- a/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/function.yaml +++ b/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: openvino.omz.semantic-segmentation-adas-0001 + name: openvino-omz-semantic-segmentation-adas-0001 namespace: cvat annotations: name: Semantic segmentation for ADAS diff --git a/serverless/openvino/omz/intel/text-detection-0004/nuclio/function.yaml b/serverless/openvino/omz/intel/text-detection-0004/nuclio/function.yaml index de273e34..7625933d 100644 --- a/serverless/openvino/omz/intel/text-detection-0004/nuclio/function.yaml +++ b/serverless/openvino/omz/intel/text-detection-0004/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: openvino.omz.intel.text-detection-0004 + name: openvino-omz-intel-text-detection-0004 namespace: cvat annotations: name: Text detection v4 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 index 546fe400..47c3f2fe 100644 --- 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 @@ -1,5 +1,5 @@ metadata: - name: openvino.omz.public.faster_rcnn_inception_v2_coco + name: openvino-omz-public-faster_rcnn_inception_v2_coco namespace: cvat annotations: name: Faster RCNN 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 index 56f04ea7..88a1a817 100644 --- 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 @@ -2,7 +2,7 @@ # 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 + name: openvino-mask-rcnn-inception-resnet-v2-atrous-coco namespace: cvat annotations: name: Mask RCNN diff --git a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/function.yaml b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/function.yaml index 13574c9b..793f2616 100644 --- a/serverless/openvino/omz/public/yolo-v3-tf/nuclio/function.yaml +++ b/serverless/openvino/omz/public/yolo-v3-tf/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: openvino.omz.public.yolo-v3-tf + name: openvino-omz-public-yolo-v3-tf namespace: cvat annotations: name: YOLO v3 diff --git a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml index cad33810..a19fdb52 100644 --- a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml +++ b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: pth.foolwood.siammask + name: pth-foolwood-siammask namespace: cvat annotations: name: SiamMask diff --git a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml index b0ef0b96..4d36482a 100644 --- a/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml +++ b/serverless/pytorch/saic-vul/fbrs/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: pth.saic-vul.fbrs + name: pth-saic-vul-fbrs namespace: cvat annotations: name: f-BRS diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml index 26ad2348..4e2cf02f 100644 --- a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: tf.faster_rcnn_inception_v2_coco + name: tf-faster-rcnn-inception-v2-coco namespace: cvat annotations: name: Faster RCNN via Tensorflow diff --git a/serverless/tensorflow/matterport/mask_rcnn/nuclio/function.yaml b/serverless/tensorflow/matterport/mask_rcnn/nuclio/function.yaml index eef8fd79..ef0bf20c 100644 --- a/serverless/tensorflow/matterport/mask_rcnn/nuclio/function.yaml +++ b/serverless/tensorflow/matterport/mask_rcnn/nuclio/function.yaml @@ -1,5 +1,5 @@ metadata: - name: tf.matterport.mask_rcnn + name: tf-matterport-mask-rcnn namespace: cvat annotations: name: Mask RCNN via Tensorflow