diff --git a/Dockerfile.ui b/Dockerfile.ui index 5c1068eb0bf..80fe3c7fd10 100644 --- a/Dockerfile.ui +++ b/Dockerfile.ui @@ -9,9 +9,8 @@ ENV TERM=xterm \ http_proxy=${http_proxy} \ https_proxy=${https_proxy} \ no_proxy=${no_proxy} \ - socks_proxy=${socks_proxy} - -ENV LANG='C.UTF-8' \ + socks_proxy=${socks_proxy} \ + LANG='C.UTF-8' \ LC_ALL='C.UTF-8' # Install dependencies diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index f85ae9ddde0..5d614948228 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -456,6 +456,27 @@ export class CanvasViewImpl implements CanvasView, Listener { e.preventDefault(); } + function contextmenuHandler(e: MouseEvent): void { + const pointID = Array.prototype.indexOf + .call(((e.target as HTMLElement).parentElement as HTMLElement).children, e.target); + if (self.activeElement.clientID !== null) { + const [state] = self.controller.objects + .filter((_state: any): boolean => ( + _state.clientID === self.activeElement.clientID + )); + self.canvas.dispatchEvent(new CustomEvent('point.contextmenu', { + bubbles: false, + cancelable: true, + detail: { + mouseEvent: e, + objectState: state, + pointID, + }, + })); + } + e.preventDefault(); + } + if (value) { (shape as any).selectize(value, { deepSelect: true, @@ -478,6 +499,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.on('dblclick', dblClickHandler); + circle.on('contextmenu', contextmenuHandler); circle.addClass('cvat_canvas_selected_point'); }); @@ -487,6 +509,7 @@ export class CanvasViewImpl implements CanvasView, Listener { }); circle.off('dblclick', dblClickHandler); + circle.off('contextmenu', contextmenuHandler); circle.removeClass('cvat_canvas_selected_point'); }); @@ -911,7 +934,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.activate(activeElement); } - if (state.points + if (state.points.length !== drawnState.points.length || state.points .some((p: number, id: number): boolean => p !== drawnState.points[id]) ) { const translatedPoints: number[] = translate(state.points); diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index defdb69e0b2..ba0ed7f6972 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -96,15 +96,21 @@ export class EditHandlerImpl implements EditHandler { let mouseY: number | null = null; this.canvas.on('mousedown.edit', (e: MouseEvent): void => { - if (e.which === 1) { + if (e.button === 0) { mouseX = e.clientX; mouseY = e.clientY; + } else if (e.button === 2 && this.editLine) { + if (this.editData.state.shapeType === 'points' + || this.editLine.attr('points').split(' ').length > 2 + ) { + (this.editLine as any).draw('undo'); + } } }); this.canvas.on('mouseup.edit', (e: MouseEvent): void => { const threshold = 10; // px - if (e.which === 1) { + if (e.button === 0) { if (Math.sqrt( // l2 distance < threshold ((mouseX - e.clientX) ** 2) + ((mouseY - e.clientY) ** 2), diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 5d6c6aae42f..1c532105c79 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -161,6 +161,11 @@ SVG.Element.prototype.resize = function constructor(...args: any): any { if (!handler) { originalResize.call(this, ...args); handler = this.remember('_resizeHandler'); + handler.resize = function(e: any) { + if (e.detail.event.button === 0) { + return handler.constructor.prototype.resize.call(this, e); + } + } handler.update = function(e: any) { this.m = this.el.node.getScreenCTM().inverse(); return handler.constructor.prototype.update.call(this, e); diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 309d387d312..e3ba2735bc9 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -317,7 +317,7 @@ // Push outside shape after each annotation shape // Any not outside shape rewrites it - if (!((object.frame + 1) in keyframes)) { + if (!((object.frame + 1) in keyframes) && object.frame + 1 <= this.stopFrame) { keyframes[object.frame + 1] = JSON .parse(JSON.stringify(keyframes[object.frame])); keyframes[object.frame + 1].outside = true; @@ -427,7 +427,10 @@ for (const object of objectsForMerge) { object.removed = true; } - }, [...objectsForMerge.map((object) => object.clientID), trackModel.clientID]); + }, [ + ...objectsForMerge + .map((object) => object.clientID), trackModel.clientID, + ], objectStates[0].frame); } split(objectState, frame) { @@ -522,7 +525,7 @@ object.removed = true; prevTrack.removed = false; nextTrack.removed = false; - }, [object.clientID, prevTrack.clientID, nextTrack.clientID]); + }, [object.clientID, prevTrack.clientID, nextTrack.clientID], frame); } group(objectStates, reset) { @@ -554,7 +557,7 @@ objectsForGroup.forEach((object, idx) => { object.group = redoGroups[idx]; }); - }, objectsForGroup.map((object) => object.clientID)); + }, objectsForGroup.map((object) => object.clientID), objectStates[0].frame); return groupIdx; } @@ -790,7 +793,7 @@ importedArray.forEach((object) => { object.removed = false; }); - }, importedArray.map((object) => object.clientID)); + }, importedArray.map((object) => object.clientID), objectStates[0].frame); } select(objectStates, x, y) { diff --git a/cvat-core/src/annotations-history.js b/cvat-core/src/annotations-history.js index d98973e5f86..4fdbf34c193 100644 --- a/cvat-core/src/annotations-history.js +++ b/cvat-core/src/annotations-history.js @@ -12,17 +12,18 @@ class AnnotationHistory { get() { return { - undo: this._undo.map((undo) => undo.action), - redo: this._redo.map((redo) => redo.action), + undo: this._undo.map((undo) => [undo.action, undo.frame]), + redo: this._redo.map((redo) => [redo.action, redo.frame]), }; } - do(action, undo, redo, clientIDs) { + do(action, undo, redo, clientIDs, frame) { const actionItem = { clientIDs, action, undo, redo, + frame, }; this._undo = this._undo.slice(-MAX_HISTORY_LENGTH + 1); diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 245d8e375c1..47c65fb9898 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -178,7 +178,7 @@ injection.groups.max = Math.max(injection.groups.max, this.group); } - _saveLock(lock) { + _saveLock(lock, frame) { const undoLock = this.lock; const redoLock = lock; @@ -186,12 +186,12 @@ this.lock = undoLock; }, () => { this.lock = redoLock; - }, [this.clientID]); + }, [this.clientID], frame); this.lock = lock; } - _saveColor(color) { + _saveColor(color, frame) { const undoColor = this.color; const redoColor = color; @@ -199,12 +199,12 @@ this.color = undoColor; }, () => { this.color = redoColor; - }, [this.clientID]); + }, [this.clientID], frame); this.color = color; } - _saveHidden(hidden) { + _saveHidden(hidden, frame) { const undoHidden = this.hidden; const redoHidden = hidden; @@ -212,12 +212,12 @@ this.hidden = undoHidden; }, () => { this.hidden = redoHidden; - }, [this.clientID]); + }, [this.clientID], frame); this.hidden = hidden; } - _saveLabel(label) { + _saveLabel(label, frame) { const undoLabel = this.label; const redoLabel = label; const undoAttributes = { ...this.attributes }; @@ -232,10 +232,10 @@ }, () => { this.label = redoLabel; this.attributes = redoAttributes; - }, [this.clientID]); + }, [this.clientID], frame); } - _saveAttributes(attributes) { + _saveAttributes(attributes, frame) { const undoAttributes = { ...this.attributes }; for (const attrID of Object.keys(attributes)) { @@ -248,7 +248,7 @@ this.attributes = undoAttributes; }, () => { this.attributes = redoAttributes; - }, [this.clientID]); + }, [this.clientID], frame); } _validateStateBeforeSave(frame, data, updated) { @@ -368,7 +368,7 @@ } } - delete(force) { + delete(frame, force) { if (!this.lock || force) { this.removed = true; @@ -376,7 +376,7 @@ this.removed = false; }, () => { this.removed = true; - }, [this.clientID]); + }, [this.clientID], frame); } return this.removed; @@ -392,7 +392,7 @@ this.shapeType = null; } - _savePinned(pinned) { + _savePinned(pinned, frame) { const undoPinned = this.pinned; const redoPinned = pinned; @@ -400,7 +400,7 @@ this.pinned = undoPinned; }, () => { this.pinned = redoPinned; - }, [this.clientID]); + }, [this.clientID], frame); this.pinned = pinned; } @@ -483,7 +483,7 @@ }; } - _savePoints(points) { + _savePoints(points, frame) { const undoPoints = this.points; const redoPoints = points; @@ -491,12 +491,12 @@ this.points = undoPoints; }, () => { this.points = redoPoints; - }, [this.clientID]); + }, [this.clientID], frame); this.points = points; } - _saveOccluded(occluded) { + _saveOccluded(occluded, frame) { const undoOccluded = this.occluded; const redoOccluded = occluded; @@ -504,12 +504,12 @@ this.occluded = undoOccluded; }, () => { this.occluded = redoOccluded; - }, [this.clientID]); + }, [this.clientID], frame); this.occluded = occluded; } - _saveZOrder(zOrder) { + _saveZOrder(zOrder, frame) { const undoZOrder = this.zOrder; const redoZOrder = zOrder; @@ -517,7 +517,7 @@ this.zOrder = undoZOrder; }, () => { this.zOrder = redoZOrder; - }, [this.clientID]); + }, [this.clientID], frame); this.zOrder = zOrder; } @@ -538,39 +538,39 @@ // Now when all fields are validated, we can apply them if (updated.label) { - this._saveLabel(data.label); + this._saveLabel(data.label, frame); } if (updated.attributes) { - this._saveAttributes(data.attributes); + this._saveAttributes(data.attributes, frame); } if (updated.points && fittedPoints.length) { - this._savePoints(fittedPoints); + this._savePoints(fittedPoints, frame); } if (updated.occluded) { - this._saveOccluded(data.occluded); + this._saveOccluded(data.occluded, frame); } if (updated.zOrder) { - this._saveZOrder(data.zOrder); + this._saveZOrder(data.zOrder, frame); } if (updated.lock) { - this._saveLock(data.lock); + this._saveLock(data.lock, frame); } if (updated.pinned) { - this._savePinned(data.pinned); + this._savePinned(data.pinned, frame); } if (updated.color) { - this._saveColor(data.color); + this._saveColor(data.color, frame); } if (updated.hidden) { - this._saveHidden(data.hidden); + this._saveHidden(data.hidden, frame); } this.updateTimestamp(updated); @@ -745,7 +745,7 @@ return result; } - _saveLabel(label) { + _saveLabel(label, frame) { const undoLabel = this.label; const redoLabel = label; const undoAttributes = { @@ -783,10 +783,10 @@ for (const mutable of redoAttributes.mutable) { this.shapes[mutable.frame].attributes = mutable.attributes; } - }, [this.clientID]); + }, [this.clientID], frame); } - _saveAttributes(frame, attributes) { + _saveAttributes(attributes, frame) { const current = this.get(frame); const labelAttributes = this.label.attributes .reduce((accumulator, value) => { @@ -858,7 +858,7 @@ if (redoShape) { this.shapes[frame] = redoShape; } - }, [this.clientID]); + }, [this.clientID], frame); } _appendShapeActionToHistory(actionType, frame, undoShape, redoShape) { @@ -874,10 +874,10 @@ } else { this.shapes[frame] = redoShape; } - }, [this.clientID]); + }, [this.clientID], frame); } - _savePoints(frame, points) { + _savePoints(points, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; @@ -921,7 +921,7 @@ ); } - _saveOccluded(frame, occluded) { + _saveOccluded(occluded, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; @@ -943,7 +943,7 @@ ); } - _saveZOrder(frame, zOrder) { + _saveZOrder(zOrder, frame) { const current = this.get(frame); const wasKeyframe = frame in this.shapes; const undoShape = wasKeyframe ? this.shapes[frame] : undefined; @@ -1007,27 +1007,27 @@ const fittedPoints = this._validateStateBeforeSave(frame, data, updated); if (updated.label) { - this._saveLabel(data.label); + this._saveLabel(data.label, frame); } if (updated.lock) { - this._saveLock(data.lock); + this._saveLock(data.lock, frame); } if (updated.pinned) { - this._savePinned(data.pinned); + this._savePinned(data.pinned, frame); } if (updated.color) { - this._saveColor(data.color); + this._saveColor(data.color, frame); } if (updated.hidden) { - this._saveHidden(data.hidden); + this._saveHidden(data.hidden, frame); } if (updated.points && fittedPoints.length) { - this._savePoints(frame, fittedPoints); + this._savePoints(fittedPoints, frame); } if (updated.outside) { @@ -1035,15 +1035,15 @@ } if (updated.occluded) { - this._saveOccluded(frame, data.occluded); + this._saveOccluded(data.occluded, frame); } if (updated.zOrder) { - this._saveZOrder(frame, data.zOrder); + this._saveZOrder(data.zOrder, frame); } if (updated.attributes) { - this._saveAttributes(frame, data.attributes); + this._saveAttributes(data.attributes, frame); } if (updated.keyframe) { @@ -1161,19 +1161,19 @@ // Now when all fields are validated, we can apply them if (updated.label) { - this._saveLabel(data.label); + this._saveLabel(data.label, frame); } if (updated.attributes) { - this._saveAttributes(data.attributes); + this._saveAttributes(data.attributes, frame); } if (updated.lock) { - this._saveLock(data.lock); + this._saveLock(data.lock, frame); } if (updated.color) { - this._saveColor(data.color); + this._saveColor(data.color, frame); } this.updateTimestamp(updated); diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js index e6a42f188af..93d17d4827a 100644 --- a/cvat-core/src/object-state.js +++ b/cvat-core/src/object-state.js @@ -398,14 +398,16 @@ * @memberof module:API.cvat.classes.ObjectState * @readonly * @instance + * @param {integer} frame current frame number * @param {boolean} [force=false] delete object even if it is locked * @async * @returns {boolean} true if object has been deleted * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} */ - async delete(force = false) { + async delete(frame, force = false) { const result = await PluginRegistry - .apiWrapper.call(this, ObjectState.prototype.delete, force); + .apiWrapper.call(this, ObjectState.prototype.delete, frame, force); return result; } } @@ -420,9 +422,13 @@ }; // Delete element from a collection which contains it - ObjectState.prototype.delete.implementation = async function (force) { + ObjectState.prototype.delete.implementation = async function (frame, force) { if (this.__internal && this.__internal.delete) { - return this.__internal.delete(force); + if (!Number.isInteger(+frame) || +frame < 0) { + throw new ArgumentError('Frame argument must be a non negative integer'); + } + + return this.__internal.delete(frame, force); } return false; diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index 17a712ecaf2..987b3faed6a 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -520,6 +520,7 @@ * @returns {HistoryActions} * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ArgumentError} + * @returns {[string, number][]} array of pairs [action name, frame number] * @instance * @async */ diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index 57fe808eca9..ae0572cfe38 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -367,7 +367,7 @@ describe('Feature: save annotations', () => { const annotations = await task.annotations.get(0); expect(task.annotations.hasUnsavedChanges()).toBe(false); - await annotations[0].delete(); + await annotations[0].delete(0); expect(task.annotations.hasUnsavedChanges()).toBe(true); await task.annotations.save(); expect(task.annotations.hasUnsavedChanges()).toBe(false); @@ -413,7 +413,7 @@ describe('Feature: save annotations', () => { const annotations = await job.annotations.get(0); expect(job.annotations.hasUnsavedChanges()).toBe(false); - await annotations[0].delete(); + await annotations[0].delete(0); expect(job.annotations.hasUnsavedChanges()).toBe(true); await job.annotations.save(); expect(job.annotations.hasUnsavedChanges()).toBe(false); @@ -436,7 +436,7 @@ describe('Feature: save annotations', () => { return result; }; - await annotations[0].delete(); + await annotations[0].delete(0); await job.annotations.save(); serverProxy.annotations.updateAnnotations = oldImplementation; diff --git a/cvat-core/tests/api/object-state.js b/cvat-core/tests/api/object-state.js index 1cf592ac4d4..92a6c8157db 100644 --- a/cvat-core/tests/api/object-state.js +++ b/cvat-core/tests/api/object-state.js @@ -289,7 +289,7 @@ describe('Feature: delete object', () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotationsBefore = await task.annotations.get(0); const { length } = annotationsBefore; - await annotationsBefore[0].delete(); + await annotationsBefore[0].delete(0); const annotationsAfter = await task.annotations.get(0); expect(annotationsAfter).toHaveLength(length - 1); }); @@ -298,7 +298,7 @@ describe('Feature: delete object', () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotationsBefore = await task.annotations.get(0); const { length } = annotationsBefore; - await annotationsBefore[0].delete(); + await annotationsBefore[0].delete(0); const annotationsAfter = await task.annotations.get(0); expect(annotationsAfter).toHaveLength(length - 1); }); diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 5c0d7ae1715..838e0b831bc 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1217,178 +1217,177 @@ } }, "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "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.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" + "@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.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==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "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.8.5" + "@webassemblyjs/wast-printer": "1.9.0" } }, "@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==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "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.8.5", - "mamacro": "^0.0.3" + "@webassemblyjs/ast": "1.9.0" } }, "@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==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "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.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" + "@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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "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.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/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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "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.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/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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "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.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" + "@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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "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.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/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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "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.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", + "@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.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "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.8.5", - "@webassemblyjs/wast-parser": "1.8.5", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", "@xtuc/long": "4.2.2" } }, @@ -2110,9 +2109,9 @@ } }, "bluebird": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", - "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "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": { @@ -2310,9 +2309,9 @@ } }, "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "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", @@ -2359,9 +2358,9 @@ "dev": true }, "cacache": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "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", @@ -2523,9 +2522,9 @@ } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "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": { @@ -2646,12 +2645,6 @@ } } }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -2811,9 +2804,9 @@ "dev": true }, "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==", + "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", @@ -3483,9 +3476,9 @@ "dev": true }, "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "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", @@ -3721,9 +3714,9 @@ "dev": true }, "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==", + "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", @@ -3769,9 +3762,9 @@ "dev": true }, "elliptic": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", - "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -4442,9 +4435,9 @@ "integrity": "sha1-7Suqu4UiJ68rz4iRUscsY8pTLrg=" }, "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", "dev": true }, "eventsource": { @@ -4750,9 +4743,9 @@ } }, "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "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": { @@ -4911,9 +4904,9 @@ "dev": true }, "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==", + "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", @@ -5017,9 +5010,9 @@ "dev": true }, "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==", + "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", @@ -5061,9 +5054,9 @@ "dev": true }, "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==", + "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", @@ -6281,13 +6274,6 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true - }, "immutable": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", @@ -6955,43 +6941,6 @@ "invert-kv": "^2.0.0" } }, - "less": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", - "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", - "dev": true, - "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.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==", - "dev": true, - "optional": true - } - } - }, - "less-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", - "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "loader-utils": "^1.1.0", - "pify": "^4.0.1" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -7167,12 +7116,6 @@ "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==", - "dev": true - }, "map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -7756,9 +7699,9 @@ "dev": true }, "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==", + "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", @@ -8278,9 +8221,9 @@ "dev": true }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "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": { @@ -8301,9 +8244,9 @@ "dev": true }, "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==", + "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", @@ -11389,9 +11332,9 @@ "dev": true }, "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==", + "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", @@ -11444,9 +11387,9 @@ "dev": true }, "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==", + "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", @@ -11470,9 +11413,9 @@ } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "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-convert": { @@ -11713,9 +11656,9 @@ } }, "terser": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", - "integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", + "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", "dev": true, "requires": { "commander": "^2.20.0", @@ -11802,9 +11745,9 @@ "dev": true }, "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==", + "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", @@ -12389,9 +12332,9 @@ } }, "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==", + "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 }, "warning": { @@ -12423,15 +12366,15 @@ } }, "webpack": { - "version": "4.41.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz", - "integrity": "sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==", + "version": "4.42.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.1.tgz", + "integrity": "sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg==", "dev": true, "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", + "@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.2.1", "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", @@ -12443,12 +12386,12 @@ "loader-utils": "^1.2.3", "memory-fs": "^0.4.1", "micromatch": "^3.1.10", - "mkdirp": "^0.5.1", + "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.1", + "terser-webpack-plugin": "^1.4.3", "watchpack": "^1.6.0", "webpack-sources": "^1.4.1" }, @@ -12459,6 +12402,21 @@ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 6b6907d2e9f..d4e08fbf2f0 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -33,9 +33,7 @@ "eslint-plugin-react": "^7.17.0", "eslint-plugin-react-hooks": "^1.7.0", "html-webpack-plugin": "^3.2.0", - "less": "^3.10.3", - "less-loader": "^5.0.0", - "node-sass": "^4.13.1", + "node-sass": "^4.13.0", "postcss-loader": "^3.0.0", "postcss-preset-env": "^6.7.0", "react-svg-loader": "^3.0.3", @@ -43,7 +41,7 @@ "style-loader": "^1.0.0", "tsconfig-paths-webpack-plugin": "^3.2.0", "typescript": "^3.7.3", - "webpack": "^4.41.2", + "webpack": "^4.42.1", "webpack-cli": "^3.3.8", "webpack-dev-server": "^3.8.0", "worker-loader": "^2.0.0" diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 0e7d81c494f..9b2a6a3c0ef 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -18,6 +18,7 @@ import { Task, FrameSpeed, Rotation, + ContextMenuType, Workspace, } from 'reducers/interfaces'; @@ -289,93 +290,21 @@ export function changeAnnotationsFilters(filters: string[]): AnyAction { }; } -export function undoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - try { - const state = getStore().getState(); - const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); - - // TODO: use affected IDs as an optimization - const [undoName] = state.annotation.annotations.history.undo.slice(-1); - const undoLog = await sessionInstance.logger.log(LogType.undoAction, { - name: undoName, - count: 1, - }, true); - await sessionInstance.actions.undo(); - const history = await sessionInstance.actions.get(); - const states = await sessionInstance.annotations - .get(frame, showAllInterpolationTracks, filters); - const [minZ, maxZ] = computeZRange(states); - await undoLog.close(); - - dispatch({ - type: AnnotationActionTypes.UNDO_ACTION_SUCCESS, - payload: { - history, - states, - minZ, - maxZ, - }, - }); - } catch (error) { - dispatch({ - type: AnnotationActionTypes.UNDO_ACTION_FAILED, - payload: { - error, - }, - }); - } - }; -} - -export function redoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - try { - const state = getStore().getState(); - const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); - - // TODO: use affected IDs as an optimization - const [redoName] = state.annotation.annotations.history.redo.slice(-1); - const redoLog = await sessionInstance.logger.log(LogType.redoAction, { - name: redoName, - count: 1, - }, true); - await sessionInstance.actions.redo(); - const history = await sessionInstance.actions.get(); - const states = await sessionInstance.annotations - .get(frame, showAllInterpolationTracks, filters); - const [minZ, maxZ] = computeZRange(states); - await redoLog.close(); - - dispatch({ - type: AnnotationActionTypes.REDO_ACTION_SUCCESS, - payload: { - history, - states, - minZ, - maxZ, - }, - }); - } catch (error) { - dispatch({ - type: AnnotationActionTypes.REDO_ACTION_FAILED, - payload: { - error, - }, - }); - } - }; -} - -export function updateCanvasContextMenu(visible: boolean, left: number, top: number): AnyAction { +export function updateCanvasContextMenu( + visible: boolean, + left: number, + top: number, + pointID: number | null = null, + type?: ContextMenuType, +): AnyAction { return { type: AnnotationActionTypes.UPDATE_CANVAS_CONTEXT_MENU, payload: { visible, left, top, + type, + pointID, }, }; } @@ -616,7 +545,9 @@ ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { await sessionInstance.logger.log(LogType.deleteObject, { count: 1 }); - const removed = await objectState.delete(force); + const { frame } = receiveAnnotationsParameters(); + + const removed = await objectState.delete(frame, force); const history = await sessionInstance.actions.get(); if (removed) { @@ -816,6 +747,90 @@ ThunkAction, {}, {}, AnyAction> { }; } +export function undoActionAsync(sessionInstance: any, frame: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const state = getStore().getState(); + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + // TODO: use affected IDs as an optimization + const [undo] = state.annotation.annotations.history.undo.slice(-1); + const undoLog = await sessionInstance.logger.log(LogType.undoAction, { + name: undo[0], + frame: undo[1], + count: 1, + }, true); + + dispatch(changeFrameAsync(undo[1])); + await sessionInstance.actions.undo(); + const history = await sessionInstance.actions.get(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + await undoLog.close(); + + dispatch({ + type: AnnotationActionTypes.UNDO_ACTION_SUCCESS, + payload: { + history, + states, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.UNDO_ACTION_FAILED, + payload: { + error, + }, + }); + } + }; +} + +export function redoActionAsync(sessionInstance: any, frame: number): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + try { + const state = getStore().getState(); + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + // TODO: use affected IDs as an optimization + const [redo] = state.annotation.annotations.history.redo.slice(-1); + const redoLog = await sessionInstance.logger.log(LogType.redoAction, { + name: redo[0], + frame: redo[1], + count: 1, + }, true); + dispatch(changeFrameAsync(redo[1])); + await sessionInstance.actions.redo(); + const history = await sessionInstance.actions.get(); + const states = await sessionInstance.annotations + .get(frame, showAllInterpolationTracks, filters); + const [minZ, maxZ] = computeZRange(states); + await redoLog.close(); + + dispatch({ + type: AnnotationActionTypes.REDO_ACTION_SUCCESS, + payload: { + history, + states, + minZ, + maxZ, + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.REDO_ACTION_FAILED, + payload: { + error, + }, + }); + } + }; +} export function rotateCurrentFrame(rotation: Rotation): AnyAction { const state: CombinedState = getStore().getState(); @@ -976,6 +991,8 @@ export function getJobAsync( export function saveAnnotationsAsync(sessionInstance: any): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { + const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); + dispatch({ type: AnnotationActionTypes.SAVE_ANNOTATIONS, payload: {}, @@ -995,6 +1012,8 @@ ThunkAction, {}, {}, AnyAction> { }); }); + const states = await sessionInstance + .annotations.get(frame, showAllInterpolationTracks, filters); await saveJobEvent.close(); await sessionInstance.logger.log( LogType.sendTaskInfo, @@ -1004,7 +1023,9 @@ ThunkAction, {}, {}, AnyAction> { dispatch({ type: AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS, - payload: {}, + payload: { + states, + }, }); } catch (error) { dispatch({ 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 5b9150a209a..de8a6222d4a 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 @@ -9,7 +9,6 @@ import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; import Select, { SelectValue } from 'antd/lib/select'; import Radio, { RadioChangeEvent } from 'antd/lib/radio'; import Input from 'antd/lib/input'; -import InputNumber from 'antd/lib/input-number'; interface InputElementParameters { attrID: number; @@ -17,7 +16,6 @@ interface InputElementParameters { values: string[]; currentValue: string; onChange(value: string): void; - ref: React.RefObject; } function renderInputElement(parameters: InputElementParameters): JSX.Element { @@ -27,7 +25,6 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { values, currentValue, onChange, - ref, } = parameters; const renderCheckbox = (): JSX.Element => ( @@ -114,7 +111,6 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { } }} onKeyDown={handleKeydown} - ref={ref as React.RefObject} /> @@ -259,8 +255,6 @@ interface Props { function AttributeEditor(props: Props): JSX.Element { const { attribute, currentValue, onChange } = props; const { inputType, values, id: attrID } = attribute; - const ref = inputType === 'number' ? React.createRef() - : React.createRef(); return (
@@ -268,7 +262,6 @@ function AttributeEditor(props: Props): JSX.Element {
{renderInputElement({ attrID, - ref, inputType, currentValue, values, 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 new file mode 100644 index 00000000000..b2cf338d263 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -0,0 +1,41 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { + Button, +} from 'antd'; + +interface Props { + activatedStateID: number | null; + visible: boolean; + left: number; + top: number; + onPointDelete(): void; +} + +export default function CanvasPointContextMenu(props: Props): JSX.Element | null { + const { + onPointDelete, + activatedStateID, + visible, + left, + top, + } = props; + + if (!visible || activatedStateID === null) { + return null; + } + + return ReactDOM.createPortal( +
+ +
, + window.document.body, + ); +} 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 16bc2e3ec14..4d55d31b104 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 @@ -4,20 +4,23 @@ import React from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; -import Slider, { SliderValue } from 'antd/lib/slider'; -import Layout from 'antd/lib/layout'; -import Icon from 'antd/lib/icon'; + import Tooltip from 'antd/lib/tooltip'; +import Icon from 'antd/lib/icon'; +import Layout from 'antd/lib/layout/layout'; +import Slider, { SliderValue } from 'antd/lib/slider'; -import { LogType } from 'cvat-logger'; -import { Canvas } from 'cvat-canvas'; -import getCore from 'cvat-core'; import { ColorBy, GridColor, ObjectType, + ContextMenuType, Workspace, + ShapeType, } from 'reducers/interfaces'; +import { LogType } from 'cvat-logger'; +import { Canvas } from 'cvat-canvas'; +import getCore from 'cvat-core'; const cvat = getCore(); @@ -51,6 +54,8 @@ interface Props { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + contextVisible: boolean; + contextType: ContextMenuType; aamZoomMargin: number; workspace: Workspace; onSetupCanvas: () => void; @@ -69,7 +74,8 @@ interface Props { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject(activatedStateID: number | null): void; onSelectObjects(selectedStatesID: number[]): void; - onUpdateContextMenu(visible: boolean, left: number, top: number): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, + pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -223,7 +229,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().removeEventListener('canvas.drawn', this.onCanvasShapeDrawn); canvasInstance.html().removeEventListener('canvas.merged', this.onCanvasObjectsMerged); canvasInstance.html().removeEventListener('canvas.groupped', this.onCanvasObjectsGroupped); - canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); + canvasInstance.html().removeEventListener('canvas.splitted', this.onCanvasTrackSplitted); + + canvasInstance.html().removeEventListener('point.contextmenu', this.onCanvasPointContextMenu); window.removeEventListener('resize', this.fitCanvas); } @@ -327,8 +335,16 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasContextMenu = (e: MouseEvent): void => { - const { activatedStateID, onUpdateContextMenu } = this.props; - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + const { + activatedStateID, + onUpdateContextMenu, + contextType, + } = this.props; + + if (contextType !== ContextMenuType.CANVAS_SHAPE_POINT) { + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, + ContextMenuType.CANVAS_SHAPE); + } }; private onCanvasShapeDragged = (e: any): void => { @@ -476,6 +492,20 @@ export default class CanvasWrapperComponent extends React.PureComponent { } }; + private onCanvasPointContextMenu = (e: any): void => { + const { + activatedStateID, + onUpdateContextMenu, + annotations, + } = this.props; + + const [state] = annotations.filter((el: any) => (el.clientID === activatedStateID)); + if (state.shapeType !== ShapeType.RECTANGLE) { + onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX, + e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID); + } + }; + private activateOnCanvas(): void { const { activatedStateID, @@ -619,6 +649,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.merged', this.onCanvasObjectsMerged); canvasInstance.html().addEventListener('canvas.groupped', this.onCanvasObjectsGroupped); canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); + + canvasInstance.html().addEventListener('point.contextmenu', this.onCanvasPointContextMenu); } public render(): 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 9ee0f8a95c3..73786bc3297 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,20 +4,16 @@ import React from 'react'; -import { - Row, - Col, - Select, - Button, - InputNumber, - Radio, -} from 'antd'; - -import { RadioChangeEvent } from 'antd/lib/radio'; +import { Row, Col } from 'antd/lib/grid'; +import Select from 'antd/lib/select'; +import Button from 'antd/lib/button'; +import InputNumber from 'antd/lib/input-number'; +import Radio, { RadioChangeEvent } from 'antd/lib/radio'; import Text from 'antd/lib/typography/Text'; import { RectDrawingMethod } from 'cvat-canvas'; import { ShapeType } from 'reducers/interfaces'; +import { clamp } from 'utils/math'; interface Props { shapeType: ShapeType; @@ -117,7 +113,15 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { { + if (typeof (value) === 'number') { + onChangePoints(Math.floor( + clamp(value, minimumPoints, Number.MAX_SAFE_INTEGER), + )); + } else if (!value) { + onChangePoints(undefined); + } + }} className='cvat-draw-shape-popover-points-selector' min={minimumPoints} value={numberOfPoints} 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 dc7952cf371..12ccd0440d9 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 @@ -4,26 +4,21 @@ import React from 'react'; -import { - Row, - Col, - Icon, - Select, - Radio, - Input, - Collapse, - Checkbox, - InputNumber, - Dropdown, - Menu, - Button, - Modal, - Popover, -} from 'antd'; - +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 { RadioChangeEvent } from 'antd/lib/radio'; -import { CheckboxChangeEvent } from 'antd/lib/checkbox'; + import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer'; import { @@ -36,9 +31,8 @@ import { ForegroundIcon, } from 'icons'; -import { - ObjectType, ShapeType, -} from 'reducers/interfaces'; +import { ObjectType, ShapeType } from 'reducers/interfaces'; +import { clamp } from 'utils/math'; function ItemMenu( serverID: number | undefined, @@ -463,7 +457,7 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element } if (attrInputType === 'number') { - const [min, max, step] = attrValues; + const [min, max, step] = attrValues.map((value: string): number => +value); return ( <> @@ -476,15 +470,17 @@ function ItemAttributeComponent(props: ItemAttributeComponentProps): JSX.Element { - if (typeof (value) !== 'undefined') { - changeAttribute(attrID, `${value}`); + if (typeof (value) === 'number') { + changeAttribute( + attrID, `${clamp(value, min, max)}`, + ); } }} value={+attrValue} className='cvat-object-item-number-attribute' - min={+min} - max={+max} - step={+step} + min={min} + max={max} + step={step} /> diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx index f47d5b12d86..66bebffae68 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx @@ -4,21 +4,21 @@ import React from 'react'; -import { - Modal, - InputNumber, -} from 'antd'; - +import Modal from 'antd/lib/modal'; +import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; +import { clamp } from 'utils/math'; interface Props { visible: boolean; propagateFrames: number; propagateUpToFrame: number; + stopFrame: number; + frameNumber: number; propagateObject(): void; cancel(): void; - changePropagateFrames(value: number | undefined): void; - changeUpToFrame(value: number | undefined): void; + changePropagateFrames(value: number): void; + changeUpToFrame(value: number): void; } export default function PropagateConfirmComponent(props: Props): JSX.Element { @@ -26,12 +26,16 @@ export default function PropagateConfirmComponent(props: Props): JSX.Element { visible, propagateFrames, propagateUpToFrame, + stopFrame, + frameNumber, propagateObject, changePropagateFrames, changeUpToFrame, cancel, } = props; + const minPropagateFrames = 1; + return (
Do you want to make a copy of the object on - + { + if (typeof (value) === 'number') { + changePropagateFrames(Math.floor( + clamp(value, minPropagateFrames, Number.MAX_SAFE_INTEGER), + )); + } + }} + /> { propagateFrames > 1 ? frames : frame } up to the - + { + if (typeof (value) === 'number') { + changeUpToFrame(Math.floor( + clamp(value, frameNumber + 1, stopFrame), + )); + } + }} + /> frame
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index e9bfd6c4e00..5cfcba47bec 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -14,6 +14,7 @@ import ControlsSideBarContainer from 'containers/annotation-page/standard-worksp import ObjectSideBarContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; import CanvasContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-context-menu'; +import CanvasPointContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-point-context-menu'; export default function StandardWorkspaceComponent(): JSX.Element { return ( @@ -23,6 +24,7 @@ export default function StandardWorkspaceComponent(): JSX.Element { + ); } diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 8a055bce823..5c6f2eba710 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -230,6 +230,21 @@ } } +.cvat-canvas-point-context-menu { + opacity: 0.6; + position: fixed; + width: 135px; + z-index: 10; + max-height: 50%; + overflow-y: auto; + background-color: #ffffff; + border-radius: 4px; + + &:hover { + opacity: 1; + } +} + .cvat-canvas-z-axis-wrapper { position: absolute; background: $background-color-2; diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx index d5f1de2ecdd..164fd614d24 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -2,20 +2,17 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { useState, useEffect } from 'react'; -import { - Row, - Col, - Icon, - Slider, - Tooltip, - InputNumber, -} from 'antd'; - -import { SliderValue } from 'antd/lib/slider'; +import { Row, Col } from 'antd/lib/grid'; +import Icon from 'antd/lib/icon'; +import Slider, { SliderValue } from 'antd/lib/slider'; +import Tooltip from 'antd/lib/tooltip'; +import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; +import { clamp } from 'utils/math'; + interface Props { startFrame: number; stopFrame: number; @@ -23,7 +20,7 @@ interface Props { frameFilename: string; inputFrameRef: React.RefObject; onSliderChange(value: SliderValue): void; - onInputChange(value: number | undefined): void; + onInputChange(value: number): void; onURLIconClick(): void; } @@ -39,6 +36,14 @@ function PlayerNavigation(props: Props): JSX.Element { onURLIconClick, } = props; + const [frameInputValue, setFrameInputValue] = useState(frameNumber); + + useEffect(() => { + if (frameNumber !== frameInputValue) { + setFrameInputValue(frameNumber); + } + }, [frameNumber]); + return ( <> @@ -70,9 +75,21 @@ function PlayerNavigation(props: Props): JSX.Element { { + if (typeof (value) === 'number') { + setFrameInputValue(Math.floor( + clamp(value, startFrame, stopFrame), + )); + } + }} + onBlur={() => { + onInputChange(frameInputValue); + }} + onPressEnter={() => { + onInputChange(frameInputValue); + }} ref={inputFrameRef} /> diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx index ede793f9023..6f4afb2b95b 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -4,12 +4,8 @@ import React from 'react'; -import { - Row, - Col, - InputNumber, -} from 'antd'; - +import { Row, Col } from 'antd/lib/grid'; +import InputNumber from 'antd/lib/input-number'; import { SliderValue } from 'antd/lib/slider'; import { Workspace } from 'reducers/interfaces'; @@ -41,7 +37,7 @@ interface Props { onFirstFrame(): void; onLastFrame(): void; onSliderChange(value: SliderValue): void; - onInputChange(value: number | undefined): void; + onInputChange(value: number): void; onURLIconClick(): void; onUndoClick(): void; onRedoClick(): void; diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index c168a0288ee..0bfc29c560d 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import 'antd/dist/antd.less'; +import 'antd/dist/antd.css'; import '../styles.scss'; import React from 'react'; import { Switch, Route, Redirect } from 'react-router'; diff --git a/cvat-ui/src/components/labels-editor/common.ts b/cvat-ui/src/components/labels-editor/common.ts index 91fd1d8bd72..5ea21f53c5e 100644 --- a/cvat-ui/src/components/labels-editor/common.ts +++ b/cvat-ui/src/components/labels-editor/common.ts @@ -5,7 +5,7 @@ export interface Attribute { id: number; name: string; - type: string; + input_type: string; mutable: boolean; values: string[]; } diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 014967a26ad..85c5ff0ddec 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -76,7 +76,7 @@ class LabelForm extends React.PureComponent { return { name: formValues.attrName[key], - type: formValues.type[key], + input_type: formValues.type[key], mutable: formValues.mutable[key], id: label && index < label.attributes.length ? label.attributes[index].id : key, @@ -136,7 +136,7 @@ class LabelForm extends React.PureComponent { private renderAttributeTypeInput(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; - const type = attr ? attr.type.toUpperCase() : AttributeType.SELECT; + const type = attr ? attr.input_type.toUpperCase() : AttributeType.SELECT; const { form } = this.props; return ( diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 06890d7500f..28274e34d2c 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -73,7 +73,7 @@ export default class LabelsEditor { id: attr.id || idGenerator(), name: attr.name, - type: attr.input_type, + input_type: attr.input_type, mutable: attr.mutable, values: [...attr.values], } @@ -207,7 +207,7 @@ export default class LabelsEditor { name: attr.name, id: attr.id < 0 ? undefined : attr.id, - input_type: attr.type.toLowerCase(), + input_type: attr.input_type.toLowerCase(), default_value: attr.values[0], mutable: attr.mutable, values: [...attr.values], diff --git a/cvat-ui/src/components/settings-page/player-settings.tsx b/cvat-ui/src/components/settings-page/player-settings.tsx index 73f2a00a8ea..779a03b01fe 100644 --- a/cvat-ui/src/components/settings-page/player-settings.tsx +++ b/cvat-ui/src/components/settings-page/player-settings.tsx @@ -4,28 +4,17 @@ import React from 'react'; -import { - Row, - Col, - Checkbox, - Slider, - Select, - InputNumber, - Icon, -} from 'antd'; - +import { Row, Col } from 'antd/lib/grid'; +import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; +import Slider from 'antd/lib/slider'; +import Select from 'antd/lib/select'; +import InputNumber from 'antd/lib/input-number'; +import Icon from 'antd/lib/icon'; import Text from 'antd/lib/typography/Text'; -import { CheckboxChangeEvent } from 'antd/lib/checkbox'; - -import { - BackJumpIcon, - ForwardJumpIcon, -} from 'icons'; -import { - FrameSpeed, - GridColor, -} from 'reducers/interfaces'; +import { clamp } from 'utils/math'; +import { BackJumpIcon, ForwardJumpIcon } from 'icons'; +import { FrameSpeed, GridColor } from 'reducers/interfaces'; interface Props { frameStep: number; @@ -78,18 +67,28 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { onChangeSaturationLevel, } = props; + const minFrameStep = 2; + const maxFrameStep = 1000; + const minGridSize = 5; + const maxGridSize = 1000; + + return (
Player step { - if (value) { - onChangeFrameStep(value); + if (typeof (value) === 'number') { + onChangeFrameStep( + Math.floor( + clamp(value, minFrameStep, maxFrameStep), + ), + ); } }} /> @@ -138,14 +137,15 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { Grid size { - if (value) { - onChangeGridSize(value); + if (typeof (value) === 'number') { + onChangeGridSize(Math.floor( + clamp(value, minGridSize, maxGridSize), + )); } }} /> diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/settings-page/workspace-settings.tsx index a7a5fd2cbb4..52ba4a5062c 100644 --- a/cvat-ui/src/components/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/components/settings-page/workspace-settings.tsx @@ -4,15 +4,12 @@ import React from 'react'; -import { - Row, - Col, - Checkbox, - InputNumber, -} from 'antd'; - +import { Row, Col } from 'antd/lib/grid'; +import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; +import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; -import { CheckboxChangeEvent } from 'antd/lib/checkbox'; + +import { clamp } from 'utils/math'; interface Props { autoSave: boolean; @@ -37,6 +34,11 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { onSwitchShowingInterpolatedTracks, } = props; + const minAutoSaveInterval = 5; + const maxAutoSaveInterval = 60; + const minAAMMargin = 0; + const maxAAMMargin = 1000; + return (
@@ -56,13 +58,19 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { Auto save every { - if (value) { - onChangeAutoSaveInterval(value * 60 * 1000); + if (typeof (value) === 'number') { + onChangeAutoSaveInterval(Math.floor( + clamp( + value, + minAutoSaveInterval, + maxAutoSaveInterval, + ), + ) * 60 * 1000); } }} /> @@ -89,12 +97,14 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { Attribute annotation mode (AAM) zoom margin { if (typeof (value) === 'number') { - onChangeAAMZoomMargin(value); + onChangeAAMZoomMargin(Math.floor( + clamp(value, minAAMMargin, maxAAMMargin), + )); } }} /> diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx index 8d2972ba66b..a47b3572489 100644 --- a/cvat-ui/src/containers/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -63,10 +63,11 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps { } } - if (searchParams.has('object')) { - const searchObject = +(searchParams.get('object') as string); - if (!Number.isNaN(searchObject)) { - initialFilters.push(`serverID==${searchObject}`); + if (searchParams.has('serverID') && searchParams.has('type')) { + const serverID = searchParams.get('serverID'); + const type = searchParams.get('type'); + if (serverID && !Number.isNaN(+serverID)) { + initialFilters.push(`serverID==${serverID} & type=="${type}"`); } } 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 fd2ff980568..3d0a508c03c 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 @@ -5,7 +5,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState, ContextMenuType } from 'reducers/interfaces'; import CanvasContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-context-menu'; @@ -14,6 +14,7 @@ interface StateToProps { visible: boolean; top: number; left: number; + type: ContextMenuType; collapsed: boolean | undefined; } @@ -29,6 +30,7 @@ function mapStateToProps(state: CombinedState): StateToProps { visible, top, left, + type, }, }, }, @@ -40,6 +42,7 @@ function mapStateToProps(state: CombinedState): StateToProps { visible, left, top, + type, }; } @@ -175,15 +178,20 @@ class CanvasContextMenuContainer extends React.PureComponent { const { visible, activatedStateID, + type, } = this.props; return ( - + <> + { type === ContextMenuType.CANVAS_SHAPE && ( + + )} + ); } } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx new file mode 100644 index 00000000000..5db5363c037 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-point-context-menu.tsx @@ -0,0 +1,193 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { connect } from 'react-redux'; +import { CombinedState, ContextMenuType } from 'reducers/interfaces'; + +import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions'; + +import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu'; + +interface StateToProps { + activatedStateID: number | null; + activatedPointID: number | null; + states: any[]; + visible: boolean; + top: number; + left: number; + type: ContextMenuType; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + annotations: { + states, + activatedStateID, + }, + canvas: { + contextMenu: { + visible, + top, + left, + type, + pointID: activatedPointID, + }, + }, + }, + } = state; + + return { + activatedStateID, + activatedPointID, + states, + 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; + +interface State { + activatedStateID: number | null; + activatedPointID: number | null; + latestLeft: number; + latestTop: number; + left: number; + top: number; +} + +class CanvasPointContextMenuContainer extends React.PureComponent { + public constructor(props: Props) { + super(props); + + this.state = { + activatedStateID: null, + activatedPointID: null, + latestLeft: 0, + latestTop: 0, + left: 0, + top: 0, + }; + } + + static getDerivedStateFromProps(props: Props, state: State): State { + const newState: State = { ...state }; + + if (props.left !== state.latestLeft + || props.top !== state.latestTop) { + newState.latestLeft = props.left; + newState.latestTop = props.top; + newState.top = props.top; + newState.left = props.left; + } + + if (typeof state.activatedStateID !== typeof props.activatedStateID + || typeof state.activatedPointID !== typeof props.activatedPointID) { + newState.activatedStateID = props.activatedStateID; + newState.activatedPointID = props.activatedPointID; + } + + + return newState; + } + + public componentDidUpdate(): void { + const { + top, + left, + } = this.state; + + const { + innerWidth, + innerHeight, + } = window; + + const [element] = window.document.getElementsByClassName('cvat-canvas-point-context-menu'); + if (element) { + const height = element.clientHeight; + const width = element.clientWidth; + + if (top + height > innerHeight || left + width > innerWidth) { + this.setState({ + top: top - Math.max(top + height - innerHeight, 0), + left: left - Math.max(left + width - innerWidth, 0), + }); + } + } + } + + private deletePoint(): void { + const { + states, + onUpdateAnnotations, + onCloseContextMenu, + } = this.props; + + const { + activatedStateID, + activatedPointID, + } = this.state; + + const [objectState] = states.filter((e) => (e.clientID === activatedStateID)); + if (typeof activatedPointID === 'number') { + objectState.points = objectState.points.slice(0, activatedPointID * 2) + .concat(objectState.points.slice(activatedPointID * 2 + 2)); + onUpdateAnnotations([objectState]); + onCloseContextMenu(); + } + } + + public render(): JSX.Element { + const { + visible, + activatedStateID, + type, + } = this.props; + + const { + top, + left, + } = this.state; + + return ( + <> + {type === ContextMenuType.CANVAS_SHAPE_POINT && ( + this.deletePoint()} + /> + )} + + ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(CanvasPointContextMenuContainer); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index e9265bfae3c..75645fc202d 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -39,6 +39,7 @@ import { GridColor, ObjectType, CombinedState, + ContextMenuType, Workspace, } from 'reducers/interfaces'; @@ -74,6 +75,8 @@ interface StateToProps { minZLayer: number; maxZLayer: number; curZLayer: number; + contextVisible: boolean; + contextType: ContextMenuType; } interface DispatchToProps { @@ -93,7 +96,8 @@ interface DispatchToProps { onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; onActivateObject: (activatedStateID: number | null) => void; onSelectObjects: (selectedStatesID: number[]) => void; - onUpdateContextMenu(visible: boolean, left: number, top: number): void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, + pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; onChangeBrightnessLevel(level: number): void; @@ -108,6 +112,10 @@ function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { canvas: { + contextMenu: { + visible: contextVisible, + type: contextType, + }, instance: canvasInstance, }, drawing: { @@ -190,6 +198,8 @@ function mapStateToProps(state: CombinedState): StateToProps { curZLayer, minZLayer, maxZLayer, + contextVisible, + contextType, workspace, }; } @@ -248,8 +258,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSelectObjects(selectedStatesID: number[]): void { dispatch(selectObjects(selectedStatesID)); }, - onUpdateContextMenu(visible: boolean, left: number, top: number): void { - dispatch(updateCanvasContextMenu(visible, left, top)); + onUpdateContextMenu(visible: boolean, left: number, top: number, + type: ContextMenuType, pointID?: number): void { + dispatch(updateCanvasContextMenu(visible, left, top, pointID, type)); }, onAddZLayer(): void { dispatch(addZLayer()); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index 868b1ddb2a8..e92c0f260dd 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -145,15 +145,9 @@ class DrawShapePopoverContainer extends React.PureComponent { }; private onChangePoints = (value: number | undefined): void => { - if (typeof (value) === 'undefined') { - this.setState({ - numberOfPoints: value, - }); - } else if (typeof (value) === 'number') { - this.setState({ - numberOfPoints: Math.max(value, this.minimumPoints), - }); - } + this.setState({ + numberOfPoints: value, + }); }; private onChangeLabel = (value: string): void => { 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 4efc88037c0..b7a189339be 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 @@ -255,7 +255,7 @@ class ObjectItemContainer extends React.PureComponent { pathname, } = window.location; - const search = `frame=${frameNumber}&object=${objectState.serverID}`; + const search = `frame=${frameNumber}&type=${objectState.objectType}&serverID=${objectState.serverID}`; const url = `${origin}${pathname}?${search}`; copy(url); }; diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx index e7c4d706318..f0de88095d7 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx @@ -88,23 +88,20 @@ class PropagateConfirmContainer extends React.PureComponent { propagateObject(jobInstance, objectState, frameNumber + 1, propagateUpToFrame); }; - private changePropagateFrames = (value: number | undefined): void => { + private changePropagateFrames = (value: number): void => { const { changePropagateFrames } = this.props; - if (typeof (value) !== 'undefined') { - changePropagateFrames(value); - } + changePropagateFrames(value); }; - private changeUpToFrame = (value: number | undefined): void => { + private changeUpToFrame = (value: number): void => { const { stopFrame, frameNumber, changePropagateFrames, } = this.props; - if (typeof (value) !== 'undefined') { - const propagateFrames = Math.max(0, Math.min(stopFrame, value)) - frameNumber; - changePropagateFrames(propagateFrames); - } + + const propagateFrames = Math.max(0, Math.min(stopFrame, value)) - frameNumber; + changePropagateFrames(propagateFrames); }; public render(): JSX.Element { @@ -122,6 +119,8 @@ class PropagateConfirmContainer extends React.PureComponent { { onChangeFrame(value as number); }; - private onChangePlayerInputValue = (value: number | undefined): void => { + private onChangePlayerInputValue = (value: number): void => { const { onSwitchPlay, onChangeFrame, playing, + frameNumber, } = this.props; - if (typeof (value) !== 'undefined') { + if (value !== frameNumber) { if (playing) { onSwitchPlay(false); } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 9b3cdbca0b5..14ac3eb6796 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -13,6 +13,7 @@ import { ActiveControl, ShapeType, ObjectType, + ContextMenuType, Workspace, } from './interfaces'; @@ -25,6 +26,8 @@ const defaultState: AnnotationState = { visible: false, left: 0, top: 0, + type: ContextMenuType.CANVAS_SHAPE, + pointID: null, }, instance: new Canvas(), ready: false, @@ -270,10 +273,12 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.SAVE_ANNOTATIONS_SUCCESS: { + const { states } = action.payload; return { ...state, annotations: { ...state.annotations, + states, saving: { ...state.annotations.saving, uploading: false, @@ -934,6 +939,8 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { visible, left, top, + type, + pointID, } = action.payload; return { @@ -945,6 +952,8 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { visible, left, top, + type, + pointID, }, }, }; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 1016dd12e1a..94d76c06f68 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -286,6 +286,7 @@ export enum StatesOrdering { export enum ContextMenuType { CANVAS = 'canvas', CANVAS_SHAPE = 'canvas_shape', + CANVAS_SHAPE_POINT = 'canvas_shape_point', } export enum Rotation { @@ -305,6 +306,8 @@ export interface AnnotationState { visible: boolean; top: number; left: number; + type: ContextMenuType; + pointID: number | null; }; instance: Canvas; ready: boolean; @@ -347,8 +350,8 @@ export interface AnnotationState { filtersHistory: string[]; resetGroupFlag: boolean; history: { - undo: string[]; - redo: string[]; + undo: [string, number][]; + redo: [string, number][]; }; saving: { uploading: boolean; diff --git a/cvat-ui/src/utils/math.ts b/cvat-ui/src/utils/math.ts new file mode 100644 index 00000000000..960bd4d1e07 --- /dev/null +++ b/cvat-ui/src/utils/math.ts @@ -0,0 +1,4 @@ +/* eslint-disable-next-line import/prefer-default-export */ +export function clamp(value: number, min: number, max: number): number { + return Math.max(Math.min(value, max), min); +} diff --git a/cvat-ui/webpack.config.js b/cvat-ui/webpack.config.js index a4d00a8e670..5d1d6d69217 100644 --- a/cvat-ui/webpack.config.js +++ b/cvat-ui/webpack.config.js @@ -50,17 +50,8 @@ module.exports = { sourceType: 'unambiguous', }, }, - }, { - test: /node_modules\/antd\/[\w\/]*.less$/, - use: ['style-loader', 'css-loader', { - loader: 'less-loader', - options: { - javascriptEnabled: true, - }, - }] }, { test: /\.(css|scss)$/, - exclude: /node_modules/, use: ['style-loader', { loader: 'css-loader', options: { diff --git a/cvat/apps/documentation/AWS-Deployment-Guide.md b/cvat/apps/documentation/AWS-Deployment-Guide.md index 79aa404ac00..1140790538a 100644 --- a/cvat/apps/documentation/AWS-Deployment-Guide.md +++ b/cvat/apps/documentation/AWS-Deployment-Guide.md @@ -4,29 +4,15 @@ There are two ways of deploying the CVAT. 1. **On Nvidia GPU Machine:** Tensorflow annotation feature is dependent on GPU hardware. One of the easy ways to launch CVAT with the tf-annotation app is to use AWS P3 instances, which provides the NVIDIA GPU. Read more about [P3 instances here.](https://aws.amazon.com/about-aws/whats-new/2017/10/introducing-amazon-ec2-p3-instances/) Overall setup instruction is explained in [main readme file](https://github.com/opencv/cvat/), except Installing Nvidia drivers. So we need to download the drivers and install it. For Amazon P3 instances, download the Nvidia Drivers from Nvidia website. For more check [Installing the NVIDIA Driver on Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-nvidia-driver.html) link. -2. **On Any other AWS Machine:** We can follow the same instruction guide mentioned in the [Readme file](https://github.com/opencv/cvat/). The additional step is to add a [security group and rule to allow incoming connections](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html). +2. **On Any other AWS Machine:** We can follow the same instruction guide mentioned in the +[installation instructions](https://github.com/opencv/cvat/blob/master/cvat/apps/documentation/installation.md). +The additional step is to add a [security group and rule to allow incoming connections](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html). -For any of above, don't forget to add exposed AWS public IP address and port to `docker-compose.override.yml`: - -You need at least 2 opened ports on your Amazon instance, for UI and Django apps. +For any of above, don't forget to add exposed AWS public IP address or hostname to `docker-compose.override.yml`: ``` version: "2.3" - -services: - cvat: + cvat_proxy: environment: - UI_HOST: *your Amazon AWS instance's url or IP* - UI_PORT: *port for UI app* - ports: - - "REACT_APP_API_PORT specified below:8080" - - - cvat_ui: - build: - args: - REACT_APP_API_HOST: *your Amazon AWS instance's url or IP* - REACT_APP_API_PORT: *port for Django app* - ports: - - "UI_PORT specified above":80" -``` \ No newline at end of file + CVAT_HOST: your-instance.amazonaws.com +``` diff --git a/cvat_proxy/nginx.conf b/cvat_proxy/nginx.conf index 289b0f6995f..105f76b02bb 100644 --- a/cvat_proxy/nginx.conf +++ b/cvat_proxy/nginx.conf @@ -10,7 +10,9 @@ http { default_type application/octet-stream; sendfile on; keepalive_timeout 65; + # For long domain names (e.g. AWS hosts) + server_names_hash_bucket_size 128; - include /etc/nginx/conf.d/*.conf; - client_max_body_size 0; + include /etc/nginx/conf.d/*.conf; + client_max_body_size 0; } diff --git a/datumaro/datumaro/cli/contexts/project/__init__.py b/datumaro/datumaro/cli/contexts/project/__init__.py index 742413cc0d3..a7ad670cd0b 100644 --- a/datumaro/datumaro/cli/contexts/project/__init__.py +++ b/datumaro/datumaro/cli/contexts/project/__init__.py @@ -167,6 +167,7 @@ def import_command(args): env = Environment() log.info("Importing project from '%s'" % args.source) + extra_args = {} if not args.format: if args.extra_args: raise CliException("Extra args can not be used without format") @@ -210,7 +211,7 @@ def import_command(args): log.info("Importing project as '%s'" % args.format) source = osp.abspath(args.source) - project = importer(source, **locals().get('extra_args', {})) + project = importer(source, **extra_args) project.config.project_name = project_name project.config.project_dir = project_dir diff --git a/datumaro/datumaro/components/dataset_filter.py b/datumaro/datumaro/components/dataset_filter.py index f5d923bef79..2d01f23a7df 100644 --- a/datumaro/datumaro/components/dataset_filter.py +++ b/datumaro/datumaro/components/dataset_filter.py @@ -48,7 +48,7 @@ def encode_annotation_base(cls, annotation): ET.SubElement(ann_elem, 'type').text = str(annotation.type.name) for k, v in annotation.attributes.items(): - ET.SubElement(ann_elem, k).text = str(v) + ET.SubElement(ann_elem, k.replace(' ', '-')).text = str(v) ET.SubElement(ann_elem, 'group').text = str(annotation.group) diff --git a/datumaro/datumaro/components/extractor.py b/datumaro/datumaro/components/extractor.py index dc7867d032d..d88d7c4551a 100644 --- a/datumaro/datumaro/components/extractor.py +++ b/datumaro/datumaro/components/extractor.py @@ -741,7 +741,22 @@ def select(self, pred): class SourceExtractor(Extractor): - pass + def __init__(self, length=None, subset=None): + super().__init__(length=length) + + if subset == DEFAULT_SUBSET_NAME: + subset = None + self._subset = subset + + def subsets(self): + if self._subset: + return [self._subset] + return None + + def get_subset(self, name): + if name != self._subset: + return None + return self class Importer: @classmethod diff --git a/datumaro/datumaro/components/project.py b/datumaro/datumaro/components/project.py index 4f23639b442..65f206e7bba 100644 --- a/datumaro/datumaro/components/project.py +++ b/datumaro/datumaro/components/project.py @@ -411,6 +411,13 @@ def _lazy_image(item): @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), + annotations=cls._merge_anno( + existing_item.annotations, current_item.annotations)) + + @staticmethod + def _merge_images(existing_item, current_item): image = None if existing_item.has_image and current_item.has_image: if existing_item.image.has_data: @@ -433,9 +440,7 @@ def _merge_items(cls, existing_item, current_item, path=None): else: image = current_item.image - return existing_item.wrap(path=path, - image=image, annotations=cls._merge_anno( - existing_item.annotations, current_item.annotations)) + return image @staticmethod def _merge_anno(a, b): @@ -527,15 +532,11 @@ def __init__(self, project): if own_source is not None: log.debug("Loading own dataset...") for item in own_source: - if not item.has_image: - existing_item = subsets[item.subset].items.get(item.id) - if existing_item is not None: - image = None - if existing_item.has_image: - # TODO: think of image comparison - image = self._lazy_image(existing_item) - item = item.wrap(path=None, - annotations=item.annotations, image=image) + existing_item = subsets[item.subset].items.get(item.id) + if existing_item is not None: + item = item.wrap(path=None, + image=self._merge_images(existing_item, item), + annotations=item.annotations) subsets[item.subset].items[item.id] = item diff --git a/datumaro/datumaro/plugins/coco_format/extractor.py b/datumaro/datumaro/plugins/coco_format/extractor.py index 730c38350dd..250404c695e 100644 --- a/datumaro/datumaro/plugins/coco_format/extractor.py +++ b/datumaro/datumaro/plugins/coco_format/extractor.py @@ -22,19 +22,22 @@ class _CocoExtractor(SourceExtractor): def __init__(self, path, task, merge_instance_polygons=False): - super().__init__() - - assert osp.isfile(path) - rootpath = path.rsplit(CocoPath.ANNOTATIONS_DIR, maxsplit=1)[0] - self._path = rootpath + assert osp.isfile(path), path + + subset = osp.splitext(osp.basename(path))[0].rsplit('_', maxsplit=1)[1] + super().__init__(subset=subset) + + rootpath = '' + if path.endswith(osp.join(CocoPath.ANNOTATIONS_DIR, osp.basename(path))): + rootpath = path.rsplit(CocoPath.ANNOTATIONS_DIR, maxsplit=1)[0] + images_dir = '' + if rootpath and osp.isdir(osp.join(rootpath, CocoPath.IMAGES_DIR)): + images_dir = osp.join(rootpath, CocoPath.IMAGES_DIR) + if osp.isdir(osp.join(images_dir, subset or DEFAULT_SUBSET_NAME)): + images_dir = osp.join(images_dir, subset or DEFAULT_SUBSET_NAME) + self._images_dir = images_dir self._task = task - subset = osp.splitext(osp.basename(path))[0] \ - .rsplit('_', maxsplit=1)[1] - if subset == DEFAULT_SUBSET_NAME: - subset = None - self._subset = subset - self._merge_instance_polygons = merge_instance_polygons loader = self._make_subset_loader(path) @@ -51,16 +54,6 @@ def __iter__(self): def __len__(self): return len(self._items) - def subsets(self): - if self._subset: - return [self._subset] - return None - - def get_subset(self, name): - if name != self._subset: - return None - return self - @staticmethod def _make_subset_loader(path): # COCO API has an 'unclosed file' warning @@ -117,9 +110,7 @@ def _load_items(self, loader): for img_id in loader.getImgIds(): image_info = loader.loadImgs(img_id)[0] - image_path = self._find_image(image_info['file_name']) - if not image_path: - image_path = image_info['file_name'] + image_path = osp.join(self._images_dir, image_info['file_name']) image_size = (image_info.get('height'), image_info.get('width')) if all(image_size): image_size = (int(image_size[0]), int(image_size[1])) @@ -232,33 +223,27 @@ def _load_annotations(self, ann, image_info=None): return parsed_annotations - def _find_image(self, file_name): - images_dir = osp.join(self._path, CocoPath.IMAGES_DIR) - search_paths = [ - osp.join(images_dir, file_name), - osp.join(images_dir, self._subset or DEFAULT_SUBSET_NAME, file_name), - ] - for image_path in search_paths: - if osp.exists(image_path): - return image_path - return None - class CocoImageInfoExtractor(_CocoExtractor): def __init__(self, path, **kwargs): - super().__init__(path, task=CocoTask.image_info, **kwargs) + kwargs['task'] = CocoTask.image_info + super().__init__(path, **kwargs) class CocoCaptionsExtractor(_CocoExtractor): def __init__(self, path, **kwargs): - super().__init__(path, task=CocoTask.captions, **kwargs) + kwargs['task'] = CocoTask.captions + super().__init__(path, **kwargs) class CocoInstancesExtractor(_CocoExtractor): def __init__(self, path, **kwargs): - super().__init__(path, task=CocoTask.instances, **kwargs) + kwargs['task'] = CocoTask.instances + super().__init__(path, **kwargs) class CocoPersonKeypointsExtractor(_CocoExtractor): def __init__(self, path, **kwargs): - super().__init__(path, task=CocoTask.person_keypoints, **kwargs) + kwargs['task'] = CocoTask.person_keypoints + super().__init__(path, **kwargs) class CocoLabelsExtractor(_CocoExtractor): def __init__(self, path, **kwargs): - super().__init__(path, task=CocoTask.labels, **kwargs) \ No newline at end of file + kwargs['task'] = CocoTask.labels + super().__init__(path, **kwargs) \ 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 014aa90f7df..0a181d7478b 100644 --- a/datumaro/datumaro/plugins/cvat_format/extractor.py +++ b/datumaro/datumaro/plugins/cvat_format/extractor.py @@ -7,8 +7,7 @@ import os.path as osp from defusedxml import ElementTree -from datumaro.components.extractor import (SourceExtractor, - DEFAULT_SUBSET_NAME, DatasetItem, +from datumaro.components.extractor import (SourceExtractor, DatasetItem, AnnotationType, Points, Polygon, PolyLine, Bbox, Label, LabelCategories ) @@ -21,9 +20,7 @@ class CvatExtractor(SourceExtractor): _SUPPORTED_SHAPES = ('box', 'polygon', 'polyline', 'points') def __init__(self, path): - super().__init__() - - assert osp.isfile(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] @@ -33,10 +30,7 @@ def __init__(self, path): self._images_dir = images_dir self._path = path - subset = osp.splitext(osp.basename(path))[0] - if subset == DEFAULT_SUBSET_NAME: - subset = None - self._subset = subset + super().__init__(subset=osp.splitext(osp.basename(path))[0]) items, categories = self._parse(path) self._items = self._load_items(items) @@ -52,16 +46,6 @@ def __iter__(self): def __len__(self): return len(self._items) - def subsets(self): - if self._subset: - return [self._subset] - return None - - def get_subset(self, name): - if name != self._subset: - return None - return self - @classmethod def _parse(cls, path): context = ElementTree.iterparse(path, events=("start", "end")) @@ -342,14 +326,8 @@ def _load_items(self, parsed): def _find_image(self, file_name): search_paths = [] if self._images_dir: - search_paths += [ - osp.join(self._images_dir, file_name), - osp.join(self._images_dir, self._subset or DEFAULT_SUBSET_NAME, - file_name), - ] - search_paths += [ - osp.join(osp.dirname(self._path), file_name) - ] + 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 diff --git a/datumaro/datumaro/plugins/datumaro_format/extractor.py b/datumaro/datumaro/plugins/datumaro_format/extractor.py index 4be7a778779..4a19565ca4f 100644 --- a/datumaro/datumaro/plugins/datumaro_format/extractor.py +++ b/datumaro/datumaro/plugins/datumaro_format/extractor.py @@ -6,8 +6,7 @@ import json import os.path as osp -from datumaro.components.extractor import (SourceExtractor, - DEFAULT_SUBSET_NAME, DatasetItem, +from datumaro.components.extractor import (SourceExtractor, DatasetItem, AnnotationType, Label, RleMask, Points, Polygon, PolyLine, Bbox, Caption, LabelCategories, MaskCategories, PointsCategories ) @@ -18,16 +17,16 @@ class DatumaroExtractor(SourceExtractor): def __init__(self, path): - super().__init__() + assert osp.isfile(path), path + rootpath = '' + if path.endswith(osp.join(DatumaroPath.ANNOTATIONS_DIR, osp.basename(path))): + rootpath = path.rsplit(DatumaroPath.ANNOTATIONS_DIR, maxsplit=1)[0] + images_dir = '' + if rootpath and osp.isdir(osp.join(rootpath, DatumaroPath.IMAGES_DIR)): + images_dir = osp.join(rootpath, DatumaroPath.IMAGES_DIR) + self._images_dir = images_dir - assert osp.isfile(path) - rootpath = path.rsplit(DatumaroPath.ANNOTATIONS_DIR, maxsplit=1)[0] - self._path = rootpath - - subset_name = osp.splitext(osp.basename(path))[0] - if subset_name == DEFAULT_SUBSET_NAME: - subset_name = None - self._subset_name = subset_name + super().__init__(subset=osp.splitext(osp.basename(path))[0]) with open(path, 'r') as f: parsed_anns = json.load(f) @@ -44,16 +43,6 @@ def __iter__(self): def __len__(self): return len(self._items) - def subsets(self): - if self._subset_name: - return [self._subset_name] - return None - - def get_subset(self, name): - if name != self._subset_name: - return None - return self - @staticmethod def _load_categories(parsed): categories = {} @@ -95,13 +84,13 @@ def _load_items(self, parsed): image = None image_info = item_desc.get('image', {}) if image_info: - image_path = osp.join(self._path, DatumaroPath.IMAGES_DIR, + image_path = osp.join(self._images_dir, image_info.get('path', '')) # relative or absolute fits image = Image(path=image_path, size=image_info.get('size')) annotations = self._load_annotations(item_desc) - item = DatasetItem(id=item_id, subset=self._subset_name, + item = DatasetItem(id=item_id, subset=self._subset, annotations=annotations, image=image) items.append(item) diff --git a/datumaro/datumaro/plugins/image_dir.py b/datumaro/datumaro/plugins/image_dir.py index c719c546f52..5f3a1884a1e 100644 --- a/datumaro/datumaro/plugins/image_dir.py +++ b/datumaro/datumaro/plugins/image_dir.py @@ -38,7 +38,7 @@ class ImageDirExtractor(SourceExtractor): def __init__(self, url): super().__init__() - assert osp.isdir(url) + assert osp.isdir(url), url items = [] for name in os.listdir(url): @@ -52,8 +52,6 @@ def __init__(self, url): items = OrderedDict(items) self._items = items - self._subsets = None - def __iter__(self): for item in self._items.values(): yield item @@ -61,9 +59,6 @@ def __iter__(self): def __len__(self): return len(self._items) - def subsets(self): - return self._subsets - def get(self, item_id, subset=None, path=None): if path or subset: raise KeyError() diff --git a/datumaro/datumaro/plugins/labelme_format.py b/datumaro/datumaro/plugins/labelme_format.py index 41069da9dab..d2512ad7a01 100644 --- a/datumaro/datumaro/plugins/labelme_format.py +++ b/datumaro/datumaro/plugins/labelme_format.py @@ -26,12 +26,8 @@ class LabelMePath: class LabelMeExtractor(SourceExtractor): def __init__(self, path, subset_name=None): - super().__init__() - - assert osp.isdir(path) - self._rootdir = path - - self._subset = subset_name + assert osp.isdir(path), path + super().__init__(subset=subset_name) items, categories = self._parse(path) self._categories = categories @@ -47,16 +43,6 @@ def __iter__(self): def __len__(self): return len(self._items) - def subsets(self): - if self._subset: - return [self._subset] - return None - - def get_subset(self, name): - if name != self._subset: - return None - return self - def _parse(self, path): categories = { AnnotationType.label: LabelCategories(attributes={ diff --git a/datumaro/datumaro/plugins/mot_format.py b/datumaro/datumaro/plugins/mot_format.py index 18d3695b145..a586645da5d 100644 --- a/datumaro/datumaro/plugins/mot_format.py +++ b/datumaro/datumaro/plugins/mot_format.py @@ -66,9 +66,7 @@ def __init__(self, path, labels=None, occlusion_threshold=0, is_gt=None): super().__init__() assert osp.isfile(path) - self._path = path seq_root = osp.dirname(osp.dirname(path)) - self._image_dir = '' if osp.isdir(osp.join(seq_root, MotPath.IMAGE_DIR)): self._image_dir = osp.join(seq_root, MotPath.IMAGE_DIR) @@ -91,8 +89,6 @@ def __init__(self, path, labels=None, occlusion_threshold=0, is_gt=None): is_gt = True self._is_gt = is_gt - self._subset = None - if labels is None: if osp.isfile(osp.join(seq_root, MotPath.LABELS_FILE)): labels = osp.join(seq_root, MotPath.LABELS_FILE) @@ -117,16 +113,6 @@ def __iter__(self): def __len__(self): return len(self._items) - def subsets(self): - if self._subset: - return [self._subset] - return None - - def get_subset(self, name): - if name != self._subset: - return None - return self - @staticmethod def _parse_labels(path): with open(path, encoding='utf-8') as labels_file: diff --git a/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py b/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py index 567392ddf8e..0f4c474b406 100644 --- a/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py +++ b/datumaro/datumaro/plugins/tf_detection_api_format/extractor.py @@ -8,8 +8,7 @@ import os.path as osp import re -from datumaro.components.extractor import (SourceExtractor, - DEFAULT_SUBSET_NAME, DatasetItem, +from datumaro.components.extractor import (SourceExtractor, DatasetItem, AnnotationType, Bbox, Mask, LabelCategories ) from datumaro.util.image import Image, decode_image, lazy_image @@ -24,9 +23,7 @@ def clamp(value, _min, _max): class TfDetectionApiExtractor(SourceExtractor): def __init__(self, path): - super().__init__() - - assert osp.isfile(path) + assert osp.isfile(path), path images_dir = '' root_dir = osp.dirname(osp.abspath(path)) if osp.basename(root_dir) == DetectionApiPath.ANNOTATIONS_DIR: @@ -35,12 +32,9 @@ def __init__(self, path): if not osp.isdir(images_dir): images_dir = '' - subset_name = osp.splitext(osp.basename(path))[0] - if subset_name == DEFAULT_SUBSET_NAME: - subset_name = None - self._subset_name = subset_name + super().__init__(subset=osp.splitext(osp.basename(path))[0]) - items, labels = self._parse_tfrecord_file(path, subset_name, images_dir) + items, labels = self._parse_tfrecord_file(path, self._subset, images_dir) self._items = items self._categories = self._load_categories(labels) @@ -54,16 +48,6 @@ def __iter__(self): def __len__(self): return len(self._items) - def subsets(self): - if self._subset_name: - return [self._subset_name] - return None - - def get_subset(self, name): - if name != self._subset_name: - return None - return self - @staticmethod def _load_categories(labels): label_categories = LabelCategories() @@ -92,7 +76,7 @@ def _parse_labelmap(cls, text): return labelmap @classmethod - def _parse_tfrecord_file(cls, filepath, subset_name, images_dir): + def _parse_tfrecord_file(cls, filepath, subset, images_dir): dataset = tf.data.TFRecordDataset(filepath) features = { 'image/filename': tf.io.FixedLenFeature([], tf.string), @@ -203,7 +187,7 @@ def _parse_tfrecord_file(cls, filepath, subset_name, images_dir): if image_params: image = Image(**image_params, size=image_size) - dataset_items.append(DatasetItem(id=item_id, subset=subset_name, + dataset_items.append(DatasetItem(id=item_id, subset=subset, image=image, annotations=annotations)) return dataset_items, dataset_labels diff --git a/datumaro/datumaro/plugins/voc_format/extractor.py b/datumaro/datumaro/plugins/voc_format/extractor.py index 29aaad6db00..96312185aad 100644 --- a/datumaro/datumaro/plugins/voc_format/extractor.py +++ b/datumaro/datumaro/plugins/voc_format/extractor.py @@ -9,8 +9,7 @@ import os.path as osp from defusedxml import ElementTree -from datumaro.components.extractor import (SourceExtractor, - DEFAULT_SUBSET_NAME, DatasetItem, +from datumaro.components.extractor import (SourceExtractor, DatasetItem, AnnotationType, Label, Mask, Bbox, CompiledMask ) from datumaro.util import dir_items @@ -26,16 +25,11 @@ class _VocExtractor(SourceExtractor): def __init__(self, path): - super().__init__() - assert osp.isfile(path), path self._path = path self._dataset_dir = osp.dirname(osp.dirname(osp.dirname(path))) - subset = osp.splitext(osp.basename(path))[0] - if subset == DEFAULT_SUBSET_NAME: - subset = None - self._subset = subset + 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 @@ -48,16 +42,6 @@ def categories(self): def __len__(self): return len(self._items) - def subsets(self): - if self._subset: - return [self._subset] - return None - - def get_subset(self, name): - if name != self._subset: - return None - return self - def _get_label_id(self, label): label_id, _ = self._categories[AnnotationType.label].find(label) assert label_id is not None, label