diff --git a/cvat-core/.eslintrc.js b/cvat-core/.eslintrc.js index a28a5cc1..9dede90d 100644 --- a/cvat-core/.eslintrc.js +++ b/cvat-core/.eslintrc.js @@ -7,7 +7,6 @@ module.exports = { '.eslintrc.js', 'webpack.config.js', 'jest.config.js', - 'jsdoc.config.js', 'src/3rdparty/**', 'node_modules/**', 'dist/**', diff --git a/cvat-core/README.md b/cvat-core/README.md index fd0f23e8..0271fc1c 100644 --- a/cvat-core/README.md +++ b/cvat-core/README.md @@ -28,12 +28,6 @@ yarn run build yarn run build --mode=development # without a minification ``` -- Building the documentation in the `docs` directory: - -```bash -yarn run docs -``` - - Running of tests: ```bash diff --git a/cvat-core/jsdoc.config.js b/cvat-core/jsdoc.config.js deleted file mode 100644 index f5c515cb..00000000 --- a/cvat-core/jsdoc.config.js +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2019-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -module.exports = { - plugins: [], - recurseDepth: 10, - source: { - includePattern: '.+\\.js(doc|x)?$', - excludePattern: '(^|\\/|\\\\)_', - }, - sourceType: 'module', - tags: { - allowUnknownTags: false, - dictionaries: ['jsdoc', 'closure'], - }, - templates: { - cleverLinks: false, - monospaceLinks: false, - default: { - outputSourceFiles: false, - }, - }, -}; diff --git a/cvat-core/package.json b/cvat-core/package.json index 4502ffcd..878542a9 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -6,7 +6,6 @@ "scripts": { "build": "webpack", "test": "jest --config=jest.config.js --coverage", - "docs": "jsdoc --readme README.md src/*.js -p -c jsdoc.config.js -d docs", "coveralls": "cat ./reports/coverage/lcov.info | coveralls", "type-check": "tsc --noEmit", "type-check:watch": "yarn run type-check --watch" @@ -23,7 +22,6 @@ "coveralls": "^3.0.5", "jest": "^26.6.3", "jest-junit": "^6.4.0", - "jsdoc": "^3.6.6", "ts-jest": "26" }, "dependencies": { diff --git a/cvat-core/src/annotation-formats.ts b/cvat-core/src/annotation-formats.ts index f8af7f3c..1fb2a6de 100644 --- a/cvat-core/src/annotation-formats.ts +++ b/cvat-core/src/annotation-formats.ts @@ -1,4 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -10,11 +11,6 @@ interface RawLoaderData { dimension: '2d' | '3d'; } -/** - * Class representing an annotation loader - * @memberof module:API.cvat.classes - * @hideconstructor -*/ export class Loader { public name: string; public format: string; @@ -33,53 +29,18 @@ export class Loader { Object.defineProperties(this, { name: { - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ get: () => data.name, }, format: { - /** - * @name format - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ get: () => data.format, }, version: { - /** - * @name version - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ get: () => data.version, }, enabled: { - /** - * @name enabled - * @type {boolean} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ get: () => data.enabled, }, dimension: { - /** - * @name dimension - * @type {module:API.cvat.enums.DimensionType} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ get: () => data.dimension, }, }); @@ -88,11 +49,6 @@ export class Loader { type RawDumperData = RawLoaderData; -/** - * Class representing an annotation dumper - * @memberof module:API.cvat.classes - * @hideconstructor - */ export class Dumper { public name: string; public format: string; @@ -111,53 +67,18 @@ export class Dumper { Object.defineProperties(this, { name: { - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ get: () => data.name, }, format: { - /** - * @name format - * @type {string} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ get: () => data.format, }, version: { - /** - * @name version - * @type {string} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ get: () => data.version, }, enabled: { - /** - * @name enabled - * @type {boolean} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ get: () => data.enabled, }, dimension: { - /** - * @name dimension - * @type {module:API.cvat.enums.DimensionType} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ get: () => data.dimension, }, }); @@ -169,11 +90,6 @@ interface AnnotationFormatRawData { exporters: RawDumperData[]; } -/** - * Class representing an annotation format - * @memberof module:API.cvat.classes - * @hideconstructor - */ export class AnnotationFormats { public loaders: Loader[]; public dumpers: Dumper[]; @@ -186,23 +102,9 @@ export class AnnotationFormats { Object.defineProperties(this, { loaders: { - /** - * @name loaders - * @type {module:API.cvat.classes.Loader[]} - * @memberof module:API.cvat.classes.AnnotationFormats - * @readonly - * @instance - */ get: () => [...data.importers], }, dumpers: { - /** - * @name dumpers - * @type {module:API.cvat.classes.Dumper[]} - * @memberof module:API.cvat.classes.AnnotationFormats - * @readonly - * @instance - */ get: () => [...data.exporters], }, }); diff --git a/cvat-core/src/annotations-collection.ts b/cvat-core/src/annotations-collection.ts index 118b8b0f..17743099 100644 --- a/cvat-core/src/annotations-collection.ts +++ b/cvat-core/src/annotations-collection.ts @@ -1,1019 +1,1045 @@ // Copyright (C) 2019-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -(() => { - const { - shapeFactory, - trackFactory, - Track, - Shape, - Tag, - } = require('./annotations-objects'); - const AnnotationsFilter = require('./annotations-filter').default; - const { checkObjectType } = require('./common'); - const Statistics = require('./statistics').default; - const { Label } = require('./labels'); - const { ArgumentError, ScriptingError } = require('./exceptions'); - const ObjectState = require('./object-state').default; - const { mask2Rle, truncateMask } = require('./object-utils'); - const config = require('./config').default; - - const { - HistoryActions, ShapeType, ObjectType, colors, Source, - } = require('./enums'); - - class Collection { - constructor(data) { - this.startFrame = data.startFrame; - this.stopFrame = data.stopFrame; - this.frameMeta = data.frameMeta; - - this.labels = data.labels.reduce((labelAccumulator, label) => { - labelAccumulator[label.id] = label; - (label?.structure?.sublabels || []).forEach((sublabel) => { - labelAccumulator[sublabel.id] = sublabel; - }); +import { + shapeFactory, trackFactory, Track, Shape, Tag, + MaskShape, BasicInjection, RawShapeData, RawTrackData, RawTagData, SkeletonShape, SkeletonTrack, +} from './annotations-objects'; +import AnnotationsFilter from './annotations-filter'; +import { checkObjectType } from './common'; +import Statistics from './statistics'; +import { Label } from './labels'; +import { ArgumentError, ScriptingError } from './exceptions'; +import ObjectState from './object-state'; +import { mask2Rle, truncateMask } from './object-utils'; +import config from './config'; +import { + HistoryActions, ShapeType, ObjectType, colors, Source, +} from './enums'; +import AnnotationHistory from './annotations-history'; + +interface RawCollection { + tags: RawTagData[], + shapes: RawShapeData[], + tracks: RawTrackData[], +} + +interface ImportedCollection { + tags: Tag[], + shapes: Shape[], + tracks: Track[], +} + +export default class Collection { + public flush: boolean; + private startFrame: number; + private stopFrame: number; + private frameMeta: any; + private labels: Record + private annotationsFilter: AnnotationsFilter; + private history: AnnotationHistory; + private shapes: Record; + private tags: Record; + private tracks: Track[]; + private objects: Record; + private count: number; + private groups: { max: number }; + private injection: BasicInjection; + + constructor(data) { + this.startFrame = data.startFrame; + this.stopFrame = data.stopFrame; + this.frameMeta = data.frameMeta; + + this.labels = data.labels.reduce((labelAccumulator, label) => { + labelAccumulator[label.id] = label; + (label?.structure?.sublabels || []).forEach((sublabel) => { + labelAccumulator[sublabel.id] = sublabel; + }); - return labelAccumulator; - }, {}); + return labelAccumulator; + }, {}); + + this.annotationsFilter = new AnnotationsFilter(); + this.history = data.history; + this.shapes = {}; // key is a frame + this.tags = {}; // key is a frame + this.tracks = []; + this.objects = {}; // key is a client id + this.count = 0; + this.flush = false; + this.groups = { + max: 0, + }; // it is an object to we can pass it as an argument by a reference + this.injection = { + labels: this.labels, + groups: this.groups, + frameMeta: this.frameMeta, + history: this.history, + nextClientID: () => ++this.count, + groupColors: {}, + getMasksOnFrame: (frame: number) => (this.shapes[frame] as MaskShape[]) + .filter((object) => object instanceof MaskShape), + }; + } - this.annotationsFilter = new AnnotationsFilter(); - this.history = data.history; - this.shapes = {}; // key is a frame - this.tags = {}; // key is a frame - this.tracks = []; - this.objects = {}; // key is a client id - this.count = 0; - this.flush = false; - this.groups = { - max: 0, - }; // it is an object to we can pass it as an argument by a reference - this.injection = { - labels: this.labels, - groups: this.groups, - frameMeta: this.frameMeta, - history: this.history, - nextClientID: () => ++this.count, - groupColors: {}, - getMasksOnFrame: (frame: number) => this.shapes[frame] - .filter((object) => object.objectShape === ObjectType.MASK), - }; + import(data: RawCollection): ImportedCollection { + const result = { + tags: [], + shapes: [], + tracks: [], + }; + + for (const tag of data.tags) { + const clientID = ++this.count; + const color = colors[clientID % colors.length]; + const tagModel = new Tag(tag, clientID, color, this.injection); + this.tags[tagModel.frame] = this.tags[tagModel.frame] || []; + this.tags[tagModel.frame].push(tagModel); + this.objects[clientID] = tagModel; + + result.tags.push(tagModel); } - import(data) { - const result = { - tags: [], - shapes: [], - tracks: [], - }; - - for (const tag of data.tags) { - const clientID = ++this.count; - const color = colors[clientID % colors.length]; - const tagModel = new Tag(tag, clientID, color, this.injection); - this.tags[tagModel.frame] = this.tags[tagModel.frame] || []; - this.tags[tagModel.frame].push(tagModel); - this.objects[clientID] = tagModel; - - result.tags.push(tagModel); - } - - for (const shape of data.shapes) { - const clientID = ++this.count; - const shapeModel = shapeFactory(shape, clientID, this.injection); - this.shapes[shapeModel.frame] = this.shapes[shapeModel.frame] || []; - this.shapes[shapeModel.frame].push(shapeModel); - this.objects[clientID] = shapeModel; - - result.shapes.push(shapeModel); - } - - for (const track of data.tracks) { - const clientID = ++this.count; - const trackModel = trackFactory(track, clientID, this.injection); - // The function can return null if track doesn't have any shapes. - // In this case a corresponded message will be sent to the console - if (trackModel) { - this.tracks.push(trackModel); - result.tracks.push(trackModel); - this.objects[clientID] = trackModel; - } - } + for (const shape of data.shapes) { + const clientID = ++this.count; + const shapeModel = shapeFactory(shape, clientID, this.injection); + this.shapes[shapeModel.frame] = this.shapes[shapeModel.frame] || []; + this.shapes[shapeModel.frame].push(shapeModel); + this.objects[clientID] = shapeModel; - return result; + result.shapes.push(shapeModel); } - export() { - const data = { - tracks: this.tracks.filter((track) => !track.removed).map((track) => track.toJSON()), - shapes: Object.values(this.shapes) - .reduce((accumulator, frameShapes) => { - accumulator.push(...frameShapes); - return accumulator; - }, []) - .filter((shape) => !shape.removed) - .map((shape) => shape.toJSON()), - tags: Object.values(this.tags) - .reduce((accumulator, frameTags) => { - accumulator.push(...frameTags); - return accumulator; - }, []) - .filter((tag) => !tag.removed) - .map((tag) => tag.toJSON()), - }; - - return data; + for (const track of data.tracks) { + const clientID = ++this.count; + const trackModel = trackFactory(track, clientID, this.injection); + // The function can return null if track doesn't have any shapes. + // In this case a corresponded message will be sent to the console + if (trackModel) { + this.tracks.push(trackModel); + result.tracks.push(trackModel); + this.objects[clientID] = trackModel; + } } - get(frame, allTracks, filters) { - const { tracks } = this; - const shapes = this.shapes[frame] || []; - const tags = this.tags[frame] || []; + return result; + } - const objects = [].concat(tracks, shapes, tags); - const visible = []; + export(): RawCollection { + const data = { + tracks: this.tracks.filter((track) => !track.removed).map((track) => track.toJSON()), + shapes: Object.values(this.shapes) + .reduce((accumulator, frameShapes) => { + accumulator.push(...frameShapes); + return accumulator; + }, []) + .filter((shape) => !shape.removed) + .map((shape) => shape.toJSON()), + tags: Object.values(this.tags) + .reduce((accumulator, frameTags) => { + accumulator.push(...frameTags); + return accumulator; + }, []) + .filter((tag) => !tag.removed) + .map((tag) => tag.toJSON()), + }; - for (const object of objects) { - if (object.removed) { - continue; - } + return data; + } - const stateData = object.get(frame); - if (stateData.outside && !stateData.keyframe && !allTracks && object instanceof Track) { - continue; - } + get(frame: number, allTracks: boolean, filters: string[]): ObjectState[] { + const { tracks } = this; + const shapes = this.shapes[frame] || []; + const tags = this.tags[frame] || []; - visible.push(stateData); - } + const objects = [].concat(tracks, shapes, tags); + const visible = []; - const objectStates = []; - const filtered = this.annotationsFilter.filter(visible, filters); + for (const object of objects) { + if (object.removed) { + continue; + } - visible.forEach((stateData, idx) => { - if (!filters.length || filtered.includes(stateData.clientID)) { - const objectState = new ObjectState(stateData); - objectStates.push(objectState); - } - }); + const stateData = object.get(frame); + if (stateData.outside && !stateData.keyframe && !allTracks && object instanceof Track) { + continue; + } - return objectStates; + visible.push(stateData); } - _mergeInternal(objectsForMerge, shapeType, label): any { - const keyframes = {}; // frame: position - const elements = {}; // element_sublabel_id: [element], each sublabel will be merged recursively + const objectStates = []; + const filtered = this.annotationsFilter.filter(visible, filters); - if (!Object.values(ShapeType).includes(shapeType)) { - throw new ArgumentError(`Got unknown shapeType "${shapeType}"`); + visible.forEach((stateData) => { + if (!filters.length || filtered.includes(stateData.clientID)) { + const objectState = new ObjectState(stateData); + objectStates.push(objectState); } + }); - const labelAttributes = label.attributes.reduce((accumulator, attribute) => { - accumulator[attribute.id] = attribute; - return accumulator; - }, {}); + return objectStates; + } - for (let i = 0; i < objectsForMerge.length; i++) { - // For each state get corresponding object - const object = objectsForMerge[i]; - if (object.label.id !== label.id) { - throw new ArgumentError( - `All object labels are expected to be "${label.name}", but got "${object.label.name}"`, - ); - } + _mergeInternal(objectsForMerge: (Track | Shape)[], shapeType: ShapeType, label: Label): RawTrackData { + const keyframes: Record = {}; // frame: position + const elements = {}; // element_sublabel_id: [element], each sublabel will be merged recursively - if (object.shapeType !== shapeType) { - throw new ArgumentError( - `All shapes are expected to be "${shapeType}", but got "${object.shapeType}"`, - ); - } + if (!Object.values(ShapeType).includes(shapeType)) { + throw new ArgumentError(`Got unknown shapeType "${shapeType}"`); + } - // If this object is shape, get it position and save as a keyframe - if (object instanceof Shape) { - // Frame already saved and it is not outside - if (object.frame in keyframes && !keyframes[object.frame].outside) { - throw new ArgumentError('Expected only one visible shape per frame'); - } + const labelAttributes = label.attributes.reduce((accumulator, attribute) => { + accumulator[attribute.id] = attribute; + return accumulator; + }, {}); + + for (let i = 0; i < objectsForMerge.length; i++) { + // For each state get corresponding object + const object = objectsForMerge[i]; + if (object.label.id !== label.id) { + throw new ArgumentError( + `All object labels are expected to be "${label.name}", but got "${object.label.name}"`, + ); + } - keyframes[object.frame] = { - type: shapeType, - frame: object.frame, - points: object.shapeType === ShapeType.SKELETON ? undefined : [...object.points], - occluded: object.occluded, - rotation: object.rotation, - z_order: object.zOrder, - outside: false, - attributes: Object.keys(object.attributes).reduce((accumulator, attrID) => { - // We save only mutable attributes inside a keyframe - if (attrID in labelAttributes && labelAttributes[attrID].mutable) { - accumulator.push({ - spec_id: +attrID, - value: object.attributes[attrID], - }); - } - return accumulator; - }, []), - }; + if (object.shapeType !== shapeType) { + throw new ArgumentError( + `All shapes are expected to be "${shapeType}", but got "${object.shapeType}"`, + ); + } - // Push outside shape after each annotation shape - // Any not outside shape will rewrite it later - 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; - keyframes[object.frame + 1].frame++; - keyframes[object.frame + 1].attributes = []; - (keyframes[object.frame + 1].elements || []).forEach((el) => { - el.outside = keyframes[object.frame + 1].outside; - el.frame = keyframes[object.frame + 1].frame; - }); - } - } else if (object instanceof Track) { - // If this object is a track, iterate through all its - // keyframes and push copies to new keyframes - const attributes = {}; // id:value - const trackShapes = object.shapes; - for (const keyframe of Object.keys(trackShapes)) { - const shape = trackShapes[keyframe]; - // Frame already saved and it is not outside - if (keyframe in keyframes && !keyframes[keyframe].outside) { - // This shape is outside and non-outside shape already exists - if (shape.outside) { - continue; - } + // If this object is shape, get it position and save as a keyframe + if (object instanceof Shape) { + // Frame already saved and it is not outside + if (object.frame in keyframes && !keyframes[object.frame].outside) { + throw new ArgumentError('Expected only one visible shape per frame'); + } - throw new ArgumentError('Expected only one visible shape per frame'); + keyframes[object.frame] = { + type: shapeType, + frame: object.frame, + points: object.shapeType === ShapeType.SKELETON ? undefined : [...object.points], + occluded: object.occluded, + rotation: object.rotation, + z_order: object.zOrder, + outside: false, + attributes: Object.keys(object.attributes).reduce((accumulator, attrID) => { + // We save only mutable attributes inside a keyframe + if (attrID in labelAttributes && labelAttributes[attrID].mutable) { + accumulator.push({ + spec_id: +attrID, + value: object.attributes[attrID], + }); } - - // We do not save an attribute if it has the same value - // We save only updates - let updatedAttributes = false; - for (const attrID in shape.attributes) { - if (!(attrID in attributes) || attributes[attrID] !== shape.attributes[attrID]) { - updatedAttributes = true; - attributes[attrID] = shape.attributes[attrID]; - } + return accumulator; + }, []), + }; + + // Push outside shape after each annotation shape + // Any not outside shape will rewrite it later + 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; + keyframes[object.frame + 1].frame++; + keyframes[object.frame + 1].attributes = []; + (keyframes[object.frame + 1].elements || []).forEach((el) => { + el.outside = keyframes[object.frame + 1].outside; + el.frame = keyframes[object.frame + 1].frame; + }); + } + } else if (object instanceof Track) { + // If this object is a track, iterate through all its + // keyframes and push copies to new keyframes + const attributes = {}; // id:value + const trackShapes = object.shapes; + for (const keyframe of Object.keys(trackShapes)) { + const shape = trackShapes[keyframe]; + // Frame already saved and it is not outside + if (keyframe in keyframes && !keyframes[keyframe].outside) { + // This shape is outside and non-outside shape already exists + if (shape.outside) { + continue; } - keyframes[keyframe] = { - type: shapeType, - frame: +keyframe, - points: object.shapeType === ShapeType.SKELETON ? undefined : [...shape.points], - rotation: shape.rotation, - occluded: shape.occluded, - outside: shape.outside, - z_order: shape.zOrder, - attributes: updatedAttributes ? Object.keys(attributes).reduce((accumulator, attrID) => { - accumulator.push({ - spec_id: +attrID, - value: attributes[attrID], - }); - - return accumulator; - }, []) : [], - }; + throw new ArgumentError('Expected only one visible shape per frame'); } - } else { - throw new ArgumentError( - `Trying to merge unknown object type: ${object.constructor.name}. ` + - 'Only shapes and tracks are expected.', - ); - } - if (object.shapeType === ShapeType.SKELETON) { - for (const element of object.elements) { - // for each track/shape element get its first objectState and keep it - elements[element.label.id] = [ - ...(elements[element.label.id] || []), element, - ]; + // We do not save an attribute if it has the same value + // We save only updates + let updatedAttributes = false; + for (const attrID in shape.attributes) { + if (!(attrID in attributes) || attributes[attrID] !== shape.attributes[attrID]) { + updatedAttributes = true; + attributes[attrID] = shape.attributes[attrID]; + } } - } - } - const mergedElements = []; - if (shapeType === ShapeType.SKELETON) { - for (const sublabel of label.structure.sublabels) { - if (!(sublabel.id in elements)) { - throw new ArgumentError( - `Merged skeleton is absent some of its elements (sublabel id: ${sublabel.id})`, - ); - } + keyframes[keyframe] = { + type: shapeType, + frame: +keyframe, + points: object.shapeType === ShapeType.SKELETON ? undefined : [...shape.points], + rotation: shape.rotation, + occluded: shape.occluded, + outside: shape.outside, + z_order: shape.zOrder, + attributes: updatedAttributes ? Object.keys(attributes).reduce((accumulator, attrID) => { + accumulator.push({ + spec_id: +attrID, + value: attributes[attrID], + }); - try { - mergedElements.push(this._mergeInternal( - elements[sublabel.id], elements[sublabel.id][0].shapeType, sublabel, - )); - } catch (error) { - throw new ArgumentError( - `Could not merge some skeleton parts (sublabel id: ${sublabel.id}). - Original error is ${error.toString()}`, - ); - } + return accumulator; + }, []) : [], + }; } + } else { + throw new ArgumentError( + `Trying to merge unknown object type: ${object.constructor.name}. ` + + 'Only shapes and tracks are expected.', + ); } - let firstNonOutside = false; - for (const frame of Object.keys(keyframes).sort((a, b) => +a - +b)) { - // Remove all outside frames at the begin - firstNonOutside = firstNonOutside || keyframes[frame].outside; - if (!firstNonOutside && keyframes[frame].outside) { - delete keyframes[frame]; - } else { - break; + if (object.shapeType === ShapeType.SKELETON) { + for (const element of (object as SkeletonShape | SkeletonTrack).elements) { + // for each track/shape element get its first objectState and keep it + elements[element.label.id] = [ + ...(elements[element.label.id] || []), element, + ]; } } - - const track = { - frame: Math.min.apply( - null, - Object.keys(keyframes).map((frame) => +frame), - ), - shapes: Object.values(keyframes), - elements: shapeType === ShapeType.SKELETON ? mergedElements : undefined, - group: 0, - source: Source.MANUAL, - label_id: label.id, - attributes: Object.keys(objectsForMerge[0].attributes).reduce((accumulator, attrID) => { - if (!labelAttributes[attrID].mutable) { - accumulator.push({ - spec_id: +attrID, - value: objectsForMerge[0].attributes[attrID], - }); - } - - return accumulator; - }, []), - }; - - return track; } - merge(objectStates) { - checkObjectType('shapes for merge', objectStates, null, Array); - if (!objectStates.length) return; - const objectsForMerge = objectStates.map((state) => { - checkObjectType('object state', state, null, ObjectState); - const object = this.objects[state.clientID]; - if (typeof object === 'undefined') { + const mergedElements = []; + if (shapeType === ShapeType.SKELETON) { + for (const sublabel of label.structure.sublabels) { + if (!(sublabel.id in elements)) { throw new ArgumentError( - 'The object is not in collection yet. Call ObjectState.put([state]) before you can merge it', + `Merged skeleton is absent some of its elements (sublabel id: ${sublabel.id})`, ); } - if (state.shapeType === ShapeType.MASK) { + try { + mergedElements.push(this._mergeInternal( + elements[sublabel.id], elements[sublabel.id][0].shapeType, sublabel, + )); + } catch (error) { throw new ArgumentError( - 'Merging for masks is not supported', + `Could not merge some skeleton parts (sublabel id: ${sublabel.id}). + Original error is ${error.toString()}`, ); } - return object; - }); + } + } - const { label, shapeType } = objectStates[0]; - if (!(label.id in this.labels)) { - throw new ArgumentError(`Unknown label for the task: ${label.id}`); + let firstNonOutside = false; + for (const frame of Object.keys(keyframes).sort((a, b) => +a - +b)) { + // Remove all outside frames at the begin + firstNonOutside = firstNonOutside || keyframes[frame].outside; + if (!firstNonOutside && keyframes[frame].outside) { + delete keyframes[frame]; + } else { + break; } + } - const track = this._mergeInternal(objectsForMerge, shapeType, label); - const imported = this.import({ - tracks: [track], - tags: [], - shapes: [], - }); + const track = { + frame: Math.min.apply( + null, + Object.keys(keyframes).map((frame) => +frame), + ), + shapes: Object.values(keyframes), + elements: shapeType === ShapeType.SKELETON ? mergedElements : undefined, + group: 0, + source: Source.MANUAL, + label_id: label.id, + attributes: Object.keys(objectsForMerge[0].attributes).reduce((accumulator, attrID) => { + if (!labelAttributes[attrID].mutable) { + accumulator.push({ + spec_id: +attrID, + value: objectsForMerge[0].attributes[attrID], + }); + } - // Remove other shapes - for (const object of objectsForMerge) { - object.removed = true; + return accumulator; + }, []), + }; + + return track; + } + + merge(objectStates: ObjectState[]): void { + checkObjectType('shapes for merge', objectStates, null, Array); + if (!objectStates.length) return; + const objectsForMerge = objectStates.map((state) => { + checkObjectType('object state', state, null, ObjectState); + const object = this.objects[state.clientID]; + if (typeof object === 'undefined') { + throw new ArgumentError( + 'The object is not in collection yet. Call ObjectState.put([state]) before you can merge it', + ); } - const [importedTrack] = imported.tracks; - this.history.do( - HistoryActions.MERGED_OBJECTS, - () => { - importedTrack.removed = true; - for (const object of objectsForMerge) { - object.removed = false; - } - }, - () => { - importedTrack.removed = false; - for (const object of objectsForMerge) { - object.removed = true; - } - }, - [...objectsForMerge.map((object) => object.clientID), importedTrack.clientID], - objectStates[0].frame, - ); + if (state.shapeType === ShapeType.MASK) { + throw new ArgumentError( + 'Merging for masks is not supported', + ); + } + return object; + }); + + const { label, shapeType } = objectStates[0]; + if (!(label.id in this.labels)) { + throw new ArgumentError(`Unknown label for the task: ${label.id}`); } - _splitInternal(objectState, object, frame): ObjectState[] { - const labelAttributes = object.label.attributes.reduce((accumulator, attribute) => { - accumulator[attribute.id] = attribute; - return accumulator; - }, {}); + const track = this._mergeInternal(objectsForMerge as (Shape | Track)[], shapeType, label); + const imported = this.import({ + tracks: [track], + tags: [], + shapes: [], + }); - // first clear all server ids which may exist in the object being splitted - const copy = trackFactory(object.toJSON(), -1, this.injection); - copy.clearServerID(); - const exported = copy.toJSON(); - - // then create two copies, before this frame and after this frame - const prev = { - frame: exported.frame, - group: 0, - label_id: exported.label_id, - attributes: exported.attributes, - shapes: [], - source: Source.MANUAL, - elements: [], - }; - - // after this frame copy is almost the same, except of starting frame - const next = JSON.parse(JSON.stringify(prev)); - next.frame = frame; - - // get position of the object on a frame where user does split and push it to next shape - const position = { - type: objectState.shapeType, - points: objectState.shapeType === ShapeType.SKELETON ? undefined : [...objectState.points], - rotation: objectState.rotation, - occluded: objectState.occluded, - outside: objectState.outside, - z_order: objectState.zOrder, - attributes: Object.keys(objectState.attributes).reduce((accumulator, attrID) => { - if (labelAttributes[attrID].mutable) { - accumulator.push({ - spec_id: +attrID, - value: objectState.attributes[attrID], - }); - } + // Remove other shapes + for (const object of objectsForMerge) { + object.removed = true; + } - return accumulator; - }, []), - frame, - }; - next.shapes.push(JSON.parse(JSON.stringify(position))); - // split all shapes of an initial object into two groups (before/after the frame) - exported.shapes.forEach((shape) => { - if (shape.frame < frame) { - prev.shapes.push(JSON.parse(JSON.stringify(shape))); - } else if (shape.frame > frame) { - next.shapes.push(JSON.parse(JSON.stringify(shape))); + const [importedTrack] = imported.tracks; + this.history.do( + HistoryActions.MERGED_OBJECTS, + () => { + importedTrack.removed = true; + for (const object of objectsForMerge) { + object.removed = false; } - }); - prev.shapes.push(JSON.parse(JSON.stringify(position))); - prev.shapes[prev.shapes.length - 1].outside = true; + }, + () => { + importedTrack.removed = false; + for (const object of objectsForMerge) { + object.removed = true; + } + }, + [...objectsForMerge.map((object) => object.clientID), importedTrack.clientID], + objectStates[0].frame, + ); + } - // do the same recursively for all objet elements if there are any + _splitInternal(objectState: ObjectState, object: Track, frame: number): RawTrackData[] { + const labelAttributes = object.label.attributes.reduce((accumulator, attribute) => { + accumulator[attribute.id] = attribute; + return accumulator; + }, {}); + + // first clear all server ids which may exist in the object being splitted + const copy = trackFactory(object.toJSON(), -1, this.injection); + copy.clearServerID(); + const exported = copy.toJSON(); + + // then create two copies, before this frame and after this frame + const prev = { + frame: exported.frame, + group: 0, + label_id: exported.label_id, + attributes: exported.attributes, + shapes: [], + source: Source.MANUAL, + elements: [], + }; + + // after this frame copy is almost the same, except of starting frame + const next = JSON.parse(JSON.stringify(prev)); + next.frame = frame; + + // get position of the object on a frame where user does split and push it to next shape + const position = { + type: objectState.shapeType, + points: objectState.shapeType === ShapeType.SKELETON ? undefined : [...objectState.points], + rotation: objectState.rotation, + occluded: objectState.occluded, + outside: objectState.outside, + z_order: objectState.zOrder, + attributes: Object.keys(objectState.attributes).reduce((accumulator, attrID) => { + if (labelAttributes[attrID].mutable) { + accumulator.push({ + spec_id: +attrID, + value: objectState.attributes[attrID], + }); + } + + return accumulator; + }, []), + frame, + }; + next.shapes.push(JSON.parse(JSON.stringify(position))); + // split all shapes of an initial object into two groups (before/after the frame) + exported.shapes.forEach((shape) => { + if (shape.frame < frame) { + prev.shapes.push(JSON.parse(JSON.stringify(shape))); + } else if (shape.frame > frame) { + next.shapes.push(JSON.parse(JSON.stringify(shape))); + } + }); + prev.shapes.push(JSON.parse(JSON.stringify(position))); + prev.shapes[prev.shapes.length - 1].outside = true; + + // do the same recursively for all object elements if there are any + + if (object instanceof SkeletonTrack) { objectState.elements.forEach((elementState, idx) => { const elementObject = object.elements[idx]; const [prevEl, nextEl] = this._splitInternal(elementState, elementObject, frame); prev.elements.push(prevEl); next.elements.push(nextEl); }); - - return [prev, next]; } - split(objectState, frame) { - checkObjectType('object state', objectState, null, ObjectState); - checkObjectType('frame', frame, 'integer', null); - - const object = this.objects[objectState.clientID]; - if (typeof object === 'undefined') { - throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); - } - - if (objectState.objectType !== ObjectType.TRACK) return; - const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b); - if (frame <= +keyframes[0]) return; - - const [prev, next] = this._splitInternal(objectState, object, frame); - const imported = this.import({ - tracks: [prev, next], - tags: [], - shapes: [], - }); + return [prev, next]; + } - // Remove source object - object.removed = true; + split(objectState: ObjectState, frame: number): void { + checkObjectType('object state', objectState, null, ObjectState); + checkObjectType('frame', frame, 'integer', null); - const [prevImported, nextImported] = imported.tracks; - this.history.do( - HistoryActions.SPLITTED_TRACK, - () => { - object.removed = false; - prevImported.removed = true; - nextImported.removed = true; - }, - () => { - object.removed = true; - prevImported.removed = false; - nextImported.removed = false; - }, - [object.clientID, prevImported.clientID, nextImported.clientID], - frame, - ); + const object = this.objects[objectState.clientID] as Track; + if (typeof object === 'undefined') { + throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); } - group(objectStates, reset) { - checkObjectType('shapes for group', objectStates, null, Array); + if (objectState.objectType !== ObjectType.TRACK) return; + const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b); + if (frame <= +keyframes[0]) return; + + const [prev, next] = this._splitInternal(objectState, object, frame); + const imported = this.import({ + tracks: [prev, next], + tags: [], + shapes: [], + }); + + // Remove source object + object.removed = true; + + const [prevImported, nextImported] = imported.tracks; + this.history.do( + HistoryActions.SPLITTED_TRACK, + () => { + object.removed = false; + prevImported.removed = true; + nextImported.removed = true; + }, + () => { + object.removed = true; + prevImported.removed = false; + nextImported.removed = false; + }, + [object.clientID, prevImported.clientID, nextImported.clientID], + frame, + ); + } - const objectsForGroup = objectStates.map((state) => { - checkObjectType('object state', state, null, ObjectState); - const object = this.objects[state.clientID]; - if (typeof object === 'undefined') { - throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); - } - return object; - }); + group(objectStates: ObjectState[], reset: boolean): number { + checkObjectType('shapes for group', objectStates, null, Array); - const groupIdx = reset ? 0 : ++this.groups.max; - const undoGroups = objectsForGroup.map((object) => object.group); - for (const object of objectsForGroup) { - object.group = groupIdx; - object.updated = Date.now(); + const objectsForGroup = objectStates.map((state) => { + checkObjectType('object state', state, null, ObjectState); + const object = this.objects[state.clientID]; + if (typeof object === 'undefined') { + throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); } - const redoGroups = objectsForGroup.map((object) => object.group); - - this.history.do( - HistoryActions.GROUPED_OBJECTS, - () => { - objectsForGroup.forEach((object, idx) => { - object.group = undoGroups[idx]; - object.updated = Date.now(); - }); - }, - () => { - objectsForGroup.forEach((object, idx) => { - object.group = redoGroups[idx]; - object.updated = Date.now(); - }); - }, - objectsForGroup.map((object) => object.clientID), - objectStates[0].frame, - ); - - return groupIdx; + return object; + }); + + const groupIdx = reset ? 0 : ++this.groups.max; + const undoGroups = objectsForGroup.map((object) => object.group); + for (const object of objectsForGroup) { + object.group = groupIdx; + object.updated = Date.now(); } + const redoGroups = objectsForGroup.map((object) => object.group); + + this.history.do( + HistoryActions.GROUPED_OBJECTS, + () => { + objectsForGroup.forEach((object, idx) => { + object.group = undoGroups[idx]; + object.updated = Date.now(); + }); + }, + () => { + objectsForGroup.forEach((object, idx) => { + object.group = redoGroups[idx]; + object.updated = Date.now(); + }); + }, + objectsForGroup.map((object) => object.clientID), + objectStates[0].frame, + ); - clear(startframe, endframe, delTrackKeyframesOnly) { - if (startframe !== undefined && endframe !== undefined) { - // If only a range of annotations need to be cleared - for (let frame = startframe; frame <= endframe; frame++) { - this.shapes[frame] = []; - this.tags[frame] = []; - } - const { tracks } = this; - tracks.forEach((track) => { - if (track.frame <= endframe) { - if (delTrackKeyframesOnly) { - for (const keyframe of Object.keys(track.shapes)) { - if (+keyframe >= startframe && +keyframe <= endframe) { - delete track.shapes[keyframe]; - (track.elements || []).forEach((element) => { - if (keyframe in element.shapes) { - delete element.shapes[keyframe]; - element.updated = Date.now(); - } - }); - track.updated = Date.now(); - } + return groupIdx; + } + + clear(startframe: number, endframe: number, delTrackKeyframesOnly: boolean): void { + if (startframe !== undefined && endframe !== undefined) { + // If only a range of annotations need to be cleared + for (let frame = startframe; frame <= endframe; frame++) { + this.shapes[frame] = []; + this.tags[frame] = []; + } + const { tracks } = this; + tracks.forEach((track) => { + if (track.frame <= endframe) { + if (delTrackKeyframesOnly) { + for (const keyframe of Object.keys(track.shapes)) { + if (+keyframe >= startframe && +keyframe <= endframe) { + delete track.shapes[keyframe]; + ((track as unknown as SkeletonTrack).elements || []).forEach((element) => { + if (keyframe in element.shapes) { + delete element.shapes[keyframe]; + element.updated = Date.now(); + } + }); + track.updated = Date.now(); } - } else if (track.frame >= startframe) { - const index = tracks.indexOf(track); - if (index > -1) { tracks.splice(index, 1); } } - } - }); - } else if (startframe === undefined && endframe === undefined) { - // If all annotations need to be cleared - this.shapes = {}; - this.tags = {}; - this.tracks = []; - this.objects = {}; - this.count = 0; - - this.flush = true; - } else { - // If inputs provided were wrong - throw Error('Could not remove the annotations, please provide both inputs or' + - ' leave the inputs below empty to remove all the annotations from this job'); - } - } - - statistics() { - const labels = {}; - const shapes = ['rectangle', 'polygon', 'polyline', 'points', 'ellipse', 'cuboid', 'skeleton']; - const body = { - ...(shapes.reduce((acc, val) => ({ - ...acc, - [val]: { shape: 0, track: 0 }, - }), {})), - - mask: { shape: 0 }, - tag: 0, - manually: 0, - interpolated: 0, - total: 0, - }; - - const sep = '{{cvat.skeleton.lbl.sep}}'; - const fillBody = (spec, prefix = ''): void => { - const pref = prefix ? `${prefix}${sep}` : ''; - for (const label of spec) { - const { name } = label; - labels[`${pref}${name}`] = JSON.parse(JSON.stringify(body)); - - if (label?.structure?.sublabels) { - fillBody(label.structure.sublabels, `${pref}${name}`); + } else if (track.frame >= startframe) { + const index = tracks.indexOf(track); + if (index > -1) { tracks.splice(index, 1); } } } - }; - - const total = JSON.parse(JSON.stringify(body)); - fillBody(Object.values(this.labels).filter((label) => !label.hasParent)); - - const scanTrack = (track, prefix = ''): void => { - const pref = prefix ? `${prefix}${sep}` : ''; - const label = `${pref}${track.label.name}`; - labels[label][track.shapeType].track++; - const keyframes = Object.keys(track.shapes) - .sort((a, b) => +a - +b) - .map((el) => +el); - - let prevKeyframe = keyframes[0]; - let visible = false; - for (const keyframe of keyframes) { - if (visible) { - const interpolated = keyframe - prevKeyframe - 1; - labels[label].interpolated += interpolated; - labels[label].total += interpolated; - } - visible = !track.shapes[keyframe].outside; - prevKeyframe = keyframe; + }); + } else if (startframe === undefined && endframe === undefined) { + // If all annotations need to be cleared + this.shapes = {}; + this.tags = {}; + this.tracks = []; + this.objects = {}; + this.count = 0; - if (visible) { - labels[label].manually++; - labels[label].total++; - } - } + this.flush = true; + } else { + // If inputs provided were wrong + throw Error('Could not remove the annotations, please provide both inputs or' + + ' leave the inputs below empty to remove all the annotations from this job'); + } + } - let lastKey = keyframes[keyframes.length - 1]; - if (track.shapeType === ShapeType.SKELETON) { - track.elements.forEach((element) => { - scanTrack(element, label); - lastKey = Math.max(lastKey, ...Object.keys(element.shapes).map((key) => +key)); - }); + statistics(): Statistics { + const labels = {}; + const shapes = ['rectangle', 'polygon', 'polyline', 'points', 'ellipse', 'cuboid', 'skeleton']; + const body = { + ...(shapes.reduce((acc, val) => ({ + ...acc, + [val]: { shape: 0, track: 0 }, + }), {})), + + mask: { shape: 0 }, + tag: 0, + manually: 0, + interpolated: 0, + total: 0, + }; + + const sep = '{{cvat.skeleton.lbl.sep}}'; + const fillBody = (spec, prefix = ''): void => { + const pref = prefix ? `${prefix}${sep}` : ''; + for (const label of spec) { + const { name } = label; + labels[`${pref}${name}`] = JSON.parse(JSON.stringify(body)); + + if (label?.structure?.sublabels) { + fillBody(label.structure.sublabels, `${pref}${name}`); } - - if (lastKey !== this.stopFrame && !track.get(lastKey).outside) { - const interpolated = this.stopFrame - lastKey; + } + }; + + const total = JSON.parse(JSON.stringify(body)); + fillBody(Object.values(this.labels).filter((label) => !label.hasParent)); + + const scanTrack = (track, prefix = ''): void => { + const pref = prefix ? `${prefix}${sep}` : ''; + const label = `${pref}${track.label.name}`; + labels[label][track.shapeType].track++; + const keyframes = Object.keys(track.shapes) + .sort((a, b) => +a - +b) + .map((el) => +el); + + let prevKeyframe = keyframes[0]; + let visible = false; + for (const keyframe of keyframes) { + if (visible) { + const interpolated = keyframe - prevKeyframe - 1; labels[label].interpolated += interpolated; labels[label].total += interpolated; } - }; + visible = !track.shapes[keyframe].outside; + prevKeyframe = keyframe; - for (const object of Object.values(this.objects)) { - if (object.removed) { - continue; + if (visible) { + labels[label].manually++; + labels[label].total++; } + } - let objectType = null; - if (object instanceof Shape) { - objectType = 'shape'; - } else if (object instanceof Track) { - objectType = 'track'; - } else if (object instanceof Tag) { - objectType = 'tag'; - } else { - throw new ScriptingError(`Unexpected object type: "${objectType}"`); - } + let lastKey = keyframes[keyframes.length - 1]; + if (track.shapeType === ShapeType.SKELETON) { + track.elements.forEach((element) => { + scanTrack(element, label); + lastKey = Math.max(lastKey, ...Object.keys(element.shapes).map((key) => +key)); + }); + } - const { name: label } = object.label; - if (objectType === 'tag') { - labels[label].tag++; - labels[label].manually++; - labels[label].total++; - } else if (objectType === 'track') { - scanTrack(object); - } else { - const { shapeType } = object; - labels[label][shapeType].shape++; - labels[label].manually++; - labels[label].total++; - if (shapeType === ShapeType.SKELETON) { - object.elements.forEach((element) => { - const combinedName = [label, element.label.name].join(sep); - labels[combinedName][element.shapeType].shape++; - labels[combinedName].manually++; - labels[combinedName].total++; - }); - } + if (lastKey !== this.stopFrame && !track.get(lastKey).outside) { + const interpolated = this.stopFrame - lastKey; + labels[label].interpolated += interpolated; + labels[label].total += interpolated; + } + }; + + for (const object of Object.values(this.objects)) { + if (object.removed) { + continue; + } + + let objectType = null; + if (object instanceof Shape) { + objectType = 'shape'; + } else if (object instanceof Track) { + objectType = 'track'; + } else if (object instanceof Tag) { + objectType = 'tag'; + } else { + throw new ScriptingError(`Unexpected object type: "${objectType}"`); + } + + const { name: label } = object.label; + if (objectType === 'tag') { + labels[label].tag++; + labels[label].manually++; + labels[label].total++; + } else if (objectType === 'track') { + scanTrack(object); + } else { + const { shapeType } = object as Shape; + labels[label][shapeType].shape++; + labels[label].manually++; + labels[label].total++; + if (shapeType === ShapeType.SKELETON) { + (object as SkeletonShape).elements.forEach((element) => { + const combinedName = [label, element.label.name].join(sep); + labels[combinedName][element.shapeType].shape++; + labels[combinedName].manually++; + labels[combinedName].total++; + }); } } + } - for (const label of Object.keys(labels)) { - for (const shapeType of Object.keys(labels[label])) { - if (typeof labels[label][shapeType] === 'object') { - for (const objectType of Object.keys(labels[label][shapeType])) { - total[shapeType][objectType] += labels[label][shapeType][objectType]; - } - } else { - total[shapeType] += labels[label][shapeType]; + for (const label of Object.keys(labels)) { + for (const shapeType of Object.keys(labels[label])) { + if (typeof labels[label][shapeType] === 'object') { + for (const objectType of Object.keys(labels[label][shapeType])) { + total[shapeType][objectType] += labels[label][shapeType][objectType]; } + } else { + total[shapeType] += labels[label][shapeType]; } } - - return new Statistics(labels, total); } - put(objectStates) { - checkObjectType('shapes for put', objectStates, null, Array); - const constructed = { - shapes: [], - tracks: [], - tags: [], - }; + return new Statistics(labels, total); + } - function convertAttributes(accumulator, attrID) { - const specID = +attrID; - const value = this.attributes[attrID]; + put(objectStates: ObjectState[]): number[] { + checkObjectType('shapes for put', objectStates, null, Array); + const constructed = { + shapes: [], + tracks: [], + tags: [], + }; - checkObjectType('attribute id', specID, 'integer', null); - checkObjectType('attribute value', value, 'string', null); + function convertAttributes(accumulator, attrID) { + const specID = +attrID; + const value = this.attributes[attrID]; - accumulator.push({ - spec_id: specID, - value, - }); + checkObjectType('attribute id', specID, 'integer', null); + checkObjectType('attribute value', value, 'string', null); + + accumulator.push({ + spec_id: specID, + value, + }); + + return accumulator; + } + for (const state of objectStates) { + checkObjectType('object state', state, null, ObjectState); + checkObjectType('state client ID', state.clientID, null, null); + checkObjectType('state frame', state.frame, 'integer', null); + checkObjectType('state rotation', state.rotation || 0, 'number', null); + checkObjectType('state attributes', state.attributes, null, Object); + checkObjectType('state label', state.label, null, Label); + + const attributes = Object.keys(state.attributes).reduce(convertAttributes.bind(state), []); + const labelAttributes = state.label.attributes.reduce((accumulator, attribute) => { + accumulator[attribute.id] = attribute; return accumulator; - } + }, {}); - for (const state of objectStates) { - checkObjectType('object state', state, null, ObjectState); - checkObjectType('state client ID', state.clientID, null, null); - checkObjectType('state frame', state.frame, 'integer', null); - checkObjectType('state rotation', state.rotation || 0, 'number', null); - checkObjectType('state attributes', state.attributes, null, Object); - checkObjectType('state label', state.label, null, Label); + // Construct whole objects from states + if (state.objectType === 'tag') { + constructed.tags.push({ + attributes, + frame: state.frame, + label_id: state.label.id, + group: 0, + }); + } else { + checkObjectType('state occluded', state.occluded, 'boolean', null); + checkObjectType('state points', state.points, null, Array); + checkObjectType('state zOrder', state.zOrder, 'integer', null); + checkObjectType('state descriptions', state.descriptions, null, Array); + state.descriptions.forEach((desc) => checkObjectType('state description', desc, 'string')); + + for (const coord of state.points) { + checkObjectType('point coordinate', coord, 'number', null); + } - const attributes = Object.keys(state.attributes).reduce(convertAttributes.bind(state), []); - const labelAttributes = state.label.attributes.reduce((accumulator, attribute) => { - accumulator[attribute.id] = attribute; - return accumulator; - }, {}); + if (!Object.values(ShapeType).includes(state.shapeType)) { + throw new ArgumentError( + `Object shape must be one of: ${JSON.stringify(Object.values(ShapeType))}`, + ); + } - // Construct whole objects from states - if (state.objectType === 'tag') { - constructed.tags.push({ + if (state.objectType === 'shape') { + constructed.shapes.push({ attributes, + descriptions: state.descriptions, frame: state.frame, - label_id: state.label.id, group: 0, + label_id: state.label.id, + occluded: state.occluded || false, + points: state.shapeType === 'mask' ? (() => { + const { width, height } = this.frameMeta[state.frame]; + const points = truncateMask(state.points, 0, width, height); + const [left, top, right, bottom] = points.splice(-4); + const rlePoints = mask2Rle(points); + rlePoints.push(left, top, right, bottom); + return rlePoints; + })() : state.points, + rotation: state.rotation || 0, + type: state.shapeType, + z_order: state.zOrder, + source: state.source, + elements: state.shapeType === 'skeleton' ? state.elements.map((element) => ({ + attributes: [], + frame: element.frame, + group: 0, + label_id: element.label.id, + points: [...element.points], + rotation: 0, + type: element.shapeType, + z_order: 0, + outside: element.outside || false, + occluded: element.occluded || false, + })) : undefined, }); - } else { - checkObjectType('state occluded', state.occluded, 'boolean', null); - checkObjectType('state points', state.points, null, Array); - checkObjectType('state zOrder', state.zOrder, 'integer', null); - checkObjectType('state descriptions', state.descriptions, null, Array); - state.descriptions.forEach((desc) => checkObjectType('state description', desc, 'string')); - - for (const coord of state.points) { - checkObjectType('point coordinate', coord, 'number', null); - } - - if (!Object.values(ShapeType).includes(state.shapeType)) { - throw new ArgumentError( - `Object shape must be one of: ${JSON.stringify(Object.values(ShapeType))}`, - ); - } + } else if (state.objectType === 'track') { + constructed.tracks.push({ + attributes: attributes.filter((attr) => !labelAttributes[attr.spec_id].mutable), + descriptions: state.descriptions, + frame: state.frame, + group: 0, + source: state.source, + label_id: state.label.id, + shapes: [ + { + attributes: attributes.filter((attr) => labelAttributes[attr.spec_id].mutable), + frame: state.frame, + occluded: false, + outside: false, + points: [...state.points], + rotation: state.rotation || 0, + type: state.shapeType, + z_order: state.zOrder, + }, + ], + elements: state.shapeType === 'skeleton' ? state.elements.map((element) => { + const elementAttrValues = Object.keys(state.attributes) + .reduce(convertAttributes.bind(state), []); + const elementAttributes = element.label.attributes.reduce((accumulator, attribute) => { + accumulator[attribute.id] = attribute; + return accumulator; + }, {}); - if (state.objectType === 'shape') { - constructed.shapes.push({ - attributes, - descriptions: state.descriptions, - frame: state.frame, - group: 0, - label_id: state.label.id, - occluded: state.occluded || false, - points: state.shapeType === 'mask' ? (() => { - const { width, height } = this.frameMeta[state.frame]; - const points = truncateMask(state.points, 0, width, height); - const [left, top, right, bottom] = points.splice(-4); - const rlePoints = mask2Rle(points); - rlePoints.push(left, top, right, bottom); - return rlePoints; - })() : state.points, - rotation: state.rotation || 0, - type: state.shapeType, - z_order: state.zOrder, - source: state.source, - elements: state.shapeType === 'skeleton' ? state.elements.map((element) => ({ - attributes: [], - frame: element.frame, + return ({ + attributes: elementAttrValues + .filter((attr) => !elementAttributes[attr.spec_id].mutable), + frame: state.frame, group: 0, label_id: element.label.id, - points: [...element.points], - rotation: 0, - type: element.shapeType, - z_order: 0, - outside: element.outside || false, - occluded: element.occluded || false, - })) : undefined, - }); - } else if (state.objectType === 'track') { - constructed.tracks.push({ - attributes: attributes.filter((attr) => !labelAttributes[attr.spec_id].mutable), - descriptions: state.descriptions, - frame: state.frame, - group: 0, - source: state.source, - label_id: state.label.id, - shapes: [ - { - attributes: attributes.filter((attr) => labelAttributes[attr.spec_id].mutable), + shapes: [{ frame: state.frame, - occluded: false, - outside: false, - points: [...state.points], - rotation: state.rotation || 0, - type: state.shapeType, - z_order: state.zOrder, - }, - ], - elements: state.shapeType === 'skeleton' ? state.elements.map((element) => { - const elementAttrValues = Object.keys(state.attributes) - .reduce(convertAttributes.bind(state), []); - const elementAttributes = element.label.attributes.reduce((accumulator, attribute) => { - accumulator[attribute.id] = attribute; - return accumulator; - }, {}); - - return ({ + type: element.shapeType, + points: [...element.points], + zOrder: state.zOrder, + outside: element.outside || false, + occluded: element.occluded || false, + rotation: element.rotation || 0, attributes: elementAttrValues .filter((attr) => !elementAttributes[attr.spec_id].mutable), - frame: state.frame, - group: 0, - label_id: element.label.id, - shapes: [{ - frame: state.frame, - type: element.shapeType, - points: [...element.points], - zOrder: state.zOrder, - outside: element.outside || false, - occluded: element.occluded || false, - rotation: element.rotation || 0, - attributes: elementAttrValues - .filter((attr) => !elementAttributes[attr.spec_id].mutable), - }], - }); - }) : undefined, - }); - } else { - throw new ArgumentError( - `Object type must be one of: ${JSON.stringify(Object.values(ObjectType))}`, - ); - } - } - } - - // Add constructed objects to a collection - // eslint-disable-next-line no-unsanitized/method - const imported = this.import(constructed); - const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes); - for (const object of importedArray) { - if (object.shapeType === ShapeType.MASK && config.removeUnderlyingMaskPixels) { - object.removeUnderlyingPixels(object.frame); + }], + }); + }) : undefined, + }); + } else { + throw new ArgumentError( + `Object type must be one of: ${JSON.stringify(Object.values(ObjectType))}`, + ); } } + } - if (objectStates.length) { - this.history.do( - HistoryActions.CREATED_OBJECTS, - () => { - importedArray.forEach((object) => { - object.removed = true; - }); - }, - () => { - importedArray.forEach((object) => { - object.removed = false; - object.serverID = undefined; - }); - }, - importedArray.map((object) => object.clientID), - objectStates[0].frame, - ); + // Add constructed objects to a collection + // eslint-disable-next-line no-unsanitized/method + const imported = this.import(constructed); + const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes); + for (const object of importedArray) { + if (object.shapeType === ShapeType.MASK && config.removeUnderlyingMaskPixels) { + (object as MaskShape).removeUnderlyingPixels(object.frame); } + } - return importedArray.map((value) => value.clientID); + if (objectStates.length) { + this.history.do( + HistoryActions.CREATED_OBJECTS, + () => { + importedArray.forEach((object) => { + object.removed = true; + }); + }, + () => { + importedArray.forEach((object) => { + object.removed = false; + object.serverID = undefined; + }); + }, + importedArray.map((object) => object.clientID), + objectStates[0].frame, + ); } - select(objectStates, x, y) { - checkObjectType('shapes for select', objectStates, null, Array); - checkObjectType('x coordinate', x, 'number', null); - checkObjectType('y coordinate', y, 'number', null); - - let minimumDistance = null; - let minimumState = null; - for (const state of objectStates) { - checkObjectType('object state', state, null, ObjectState); - if (state.outside || state.hidden || state.objectType === ObjectType.TAG) { - continue; - } + return importedArray.map((value) => value.clientID); + } - const object = this.objects[state.clientID]; - if (typeof object === 'undefined') { - throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); - } - const distance = object.constructor.distance(state.points, x, y, state.rotation); - if (distance !== null && (minimumDistance === null || distance < minimumDistance)) { - minimumDistance = distance; - minimumState = state; - } + select(objectStates: ObjectState[], x: number, y: number): { + state: ObjectState, + distance: number | null, + } { + checkObjectType('shapes for select', objectStates, null, Array); + checkObjectType('x coordinate', x, 'number', null); + checkObjectType('y coordinate', y, 'number', null); + + let minimumDistance = null; + let minimumState = null; + for (const state of objectStates) { + checkObjectType('object state', state, null, ObjectState); + if (state.outside || state.hidden || state.objectType === ObjectType.TAG) { + continue; } - return { - state: minimumState, - distance: minimumDistance, - }; + const object = this.objects[state.clientID]; + if (typeof object === 'undefined') { + throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); + } + const distance = object.constructor.distance(state.points, x, y, state.rotation); + if (distance !== null && (minimumDistance === null || distance < minimumDistance)) { + minimumDistance = distance; + minimumState = state; + } } - searchEmpty(frameFrom, frameTo) { - const sign = Math.sign(frameTo - frameFrom); - const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; - const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1; - for (let frame = frameFrom; predicate(frame); frame = update(frame)) { - if (frame in this.shapes && this.shapes[frame].some((shape) => !shape.removed)) { - continue; - } - if (frame in this.tags && this.tags[frame].some((tag) => !tag.removed)) { - continue; - } - const filteredTracks = this.tracks.filter((track) => !track.removed); - let found = false; - for (const track of filteredTracks) { - const keyframes = track.boundedKeyframes(frame); - const { prev, first } = keyframes; - const last = prev === null ? first : prev; - const lastShape = track.shapes[last]; - const isKeyfame = frame in track.shapes; - if (first <= frame && (!lastShape.outside || isKeyfame)) { - found = true; - break; - } - } - - if (found) continue; + return { + state: minimumState, + distance: minimumDistance, + }; + } - return frame; + searchEmpty(frameFrom: number, frameTo: number): number | null { + const sign = Math.sign(frameTo - frameFrom); + const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; + const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1; + for (let frame = frameFrom; predicate(frame); frame = update(frame)) { + if (frame in this.shapes && this.shapes[frame].some((shape) => !shape.removed)) { + continue; + } + if (frame in this.tags && this.tags[frame].some((tag) => !tag.removed)) { + continue; } + const filteredTracks = this.tracks.filter((track) => !track.removed); + let found = false; + for (const track of filteredTracks) { + const keyframes = track.boundedKeyframes(frame); + const { prev, first } = keyframes; + const last = prev === null ? first : prev; + const lastShape = track.shapes[last]; + const isKeyfame = frame in track.shapes; + if (first <= frame && (!lastShape.outside || isKeyfame)) { + found = true; + break; + } + } + + if (found) continue; - return null; + return frame; } - search(filters, frameFrom, frameTo) { - const sign = Math.sign(frameTo - frameFrom); - const filtersStr = JSON.stringify(filters); - const linearSearch = filtersStr.match(/"var":"width"/) || filtersStr.match(/"var":"height"/); - - const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; - const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1; - for (let frame = frameFrom; predicate(frame); frame = update(frame)) { - // First prepare all data for the frame - // Consider all shapes, tags, and not outside tracks that have keyframe here - // In particular consider first and last frame as keyframes for all tracks - const statesData = [].concat( - (frame in this.shapes ? this.shapes[frame] : []) - .filter((shape) => !shape.removed) - .map((shape) => shape.get(frame)), - (frame in this.tags ? this.tags[frame] : []) - .filter((tag) => !tag.removed) - .map((tag) => tag.get(frame)), - ); - const tracks = Object.values(this.tracks) - .filter((track) => ( - frame in track.shapes || frame === frameFrom || - frame === frameTo || linearSearch)) - .filter((track) => !track.removed); - statesData.push(...tracks.map((track) => track.get(frame)).filter((state) => !state.outside)); - - // Nothing to filtering, go to the next iteration - if (!statesData.length) { - continue; - } + return null; + } - // Filtering - const filtered = this.annotationsFilter.filter(statesData, filters); - if (filtered.length) { - return frame; - } + search(filters: string[], frameFrom: number, frameTo: number): number | null { + const sign = Math.sign(frameTo - frameFrom); + const filtersStr = JSON.stringify(filters); + const linearSearch = filtersStr.match(/"var":"width"/) || filtersStr.match(/"var":"height"/); + + const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; + const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1; + for (let frame = frameFrom; predicate(frame); frame = update(frame)) { + // First prepare all data for the frame + // Consider all shapes, tags, and not outside tracks that have keyframe here + // In particular consider first and last frame as keyframes for all tracks + const statesData = [].concat( + (frame in this.shapes ? this.shapes[frame] : []) + .filter((shape) => !shape.removed) + .map((shape) => shape.get(frame)), + (frame in this.tags ? this.tags[frame] : []) + .filter((tag) => !tag.removed) + .map((tag) => tag.get(frame)), + ); + const tracks = Object.values(this.tracks) + .filter((track) => ( + frame in track.shapes || frame === frameFrom || + frame === frameTo || linearSearch)) + .filter((track) => !track.removed); + statesData.push(...tracks.map((track) => track.get(frame)).filter((state) => !state.outside)); + + // Nothing to filtering, go to the next iteration + if (!statesData.length) { + continue; } - return null; + // Filtering + const filtered = this.annotationsFilter.filter(statesData, filters); + if (filtered.length) { + return frame; + } } - } - module.exports = Collection; -})(); + return null; + } +} diff --git a/cvat-core/src/annotations-objects.ts b/cvat-core/src/annotations-objects.ts index bca6a83d..fea9be2a 100644 --- a/cvat-core/src/annotations-objects.ts +++ b/cvat-core/src/annotations-objects.ts @@ -31,8 +31,8 @@ function copyShape(state: TrackedShape, data: Partial = {}): Track }; } -interface AnnotationInjection { - labels: Label[]; +export interface BasicInjection { + labels: Record; groups: { max: number }; frameMeta: { deleted_frames: Record; @@ -45,23 +45,28 @@ interface AnnotationInjection { getMasksOnFrame: (frame: number) => MaskShape[]; } +type AnnotationInjection = BasicInjection & { + parentID?: number; + readOnlyFields?: string[]; +}; + class Annotation { public clientID: number; protected taskLabels: Label[]; protected history: any; protected groupColors: Record; - protected serverID: number | null; + public serverID: number | null; protected parentID: number | null; - protected group: number; + public group: number; public label: Label; - protected frame: number; + public frame: number; private _removed: boolean; public lock: boolean; protected readOnlyFields: string[]; protected color: string; protected source: Source; public updated: number; - protected attributes: Record; + public attributes: Record; protected groupObject: { color: string; readonly id: number; @@ -366,7 +371,7 @@ class Drawn extends Annotation { protected descriptions: string[]; public hidden: boolean; protected pinned: boolean; - protected shapeType: ShapeType; + public shapeType: ShapeType; constructor(data, clientID: number, color: string, injection: AnnotationInjection) { super(data, clientID, color, injection); @@ -472,7 +477,7 @@ class Drawn extends Annotation { } } -interface RawShapeData { +export interface RawShapeData { id?: number; clientID?: number; label_id: number; @@ -501,8 +506,8 @@ export class Shape extends Drawn { public points: number[]; public occluded: boolean; public outside: boolean; - protected rotation: number; - protected zOrder: number; + public rotation: number; + public zOrder: number; constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { super(data, clientID, color, injection); @@ -787,7 +792,7 @@ export class Shape extends Drawn { } } -interface RawTrackData { +export interface RawTrackData { id?: number; clientID?: number; label_id: number; @@ -1415,7 +1420,7 @@ export class Track extends Drawn { } } -interface RawTagData { +export interface RawTagData { id?: number; clientID?: number; label_id: number; @@ -1867,7 +1872,7 @@ export class CuboidShape extends Shape { } export class SkeletonShape extends Shape { - private elements: Shape[]; + public elements: Shape[]; constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { super(data, clientID, color, injection); @@ -2186,7 +2191,7 @@ export class MaskShape extends Shape { return []; } - protected removeUnderlyingPixels(frame: number): void { + public removeUnderlyingPixels(frame: number): void { if (frame !== this.frame) { throw new ArgumentError( `Wrong "frame" attribute: is not equal to the shape frame (${frame} vs ${this.frame})`, @@ -2726,7 +2731,7 @@ export class CuboidTrack extends Track { } export class SkeletonTrack extends Track { - private elements: Track[]; + public elements: Track[]; constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { super(data, clientID, color, injection); @@ -3069,7 +3074,7 @@ Object.defineProperty(EllipseTrack, 'distance', { value: EllipseShape.distance } Object.defineProperty(CuboidTrack, 'distance', { value: CuboidShape.distance }); Object.defineProperty(SkeletonTrack, 'distance', { value: SkeletonShape.distance }); -export function shapeFactory(data: RawShapeData, clientID: number, injection: AnnotationInjection): Annotation { +export function shapeFactory(data: RawShapeData, clientID: number, injection: AnnotationInjection): Shape { const { type } = data; const color = colors[clientID % colors.length]; @@ -3106,7 +3111,7 @@ export function shapeFactory(data: RawShapeData, clientID: number, injection: An return shapeModel; } -export function trackFactory(trackData: RawTrackData, clientID: number, injection: AnnotationInjection): Annotation { +export function trackFactory(trackData: RawTrackData, clientID: number, injection: AnnotationInjection): Track { if (trackData.shapes.length) { const { type } = trackData.shapes[0]; const color = colors[clientID % colors.length]; diff --git a/cvat-core/src/annotations-saver.ts b/cvat-core/src/annotations-saver.ts index 41800d6a..bc4da408 100644 --- a/cvat-core/src/annotations-saver.ts +++ b/cvat-core/src/annotations-saver.ts @@ -1,261 +1,264 @@ // Copyright (C) 2019-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corp +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -(() => { - const serverProxy = require('./server-proxy').default; - const { Task } = require('./session'); - const { ScriptingError } = require('./exceptions'); - - class AnnotationsSaver { - constructor(version, collection, session) { - this.sessionType = session instanceof Task ? 'task' : 'job'; - this.id = session.id; - this.version = version; - this.collection = collection; - this.initialObjects = {}; - this.hash = this._getHash(); - - // We need use data from export instead of initialData - // Otherwise we have differ keys order and JSON comparison code incorrect - const exported = this.collection.export(); - - this._resetState(); - for (const shape of exported.shapes) { - this.initialObjects.shapes[shape.id] = shape; - } - - for (const track of exported.tracks) { - this.initialObjects.tracks[track.id] = track; - } - - for (const tag of exported.tags) { - this.initialObjects.tags[tag.id] = tag; - } +import serverProxy from './server-proxy'; +import { Task } from './session'; +import { ScriptingError } from './exceptions'; + +export default class AnnotationsSaver { + private sessionType: 'task' | 'job'; + private id: number; + private version: number; + private collection: any; + private hash: string; + private initialObjects: any; + + constructor(version, collection, session) { + this.sessionType = session instanceof Task ? 'task' : 'job'; + this.id = session.id; + this.version = version; + this.collection = collection; + this.initialObjects = {}; + this.hash = this._getHash(); + + // We need use data from export instead of initialData + // Otherwise we have differ keys order and JSON comparison code incorrect + const exported = this.collection.export(); + + this._resetState(); + for (const shape of exported.shapes) { + this.initialObjects.shapes[shape.id] = shape; } - _resetState() { - this.initialObjects = { - shapes: {}, - tracks: {}, - tags: {}, - }; + for (const track of exported.tracks) { + this.initialObjects.tracks[track.id] = track; } - _getHash() { - const exported = this.collection.export(); - return JSON.stringify(exported); + for (const tag of exported.tags) { + this.initialObjects.tags[tag.id] = tag; } + } - async _request(data, action) { - const result = await serverProxy.annotations.updateAnnotations(this.sessionType, this.id, data, action); + _resetState() { + this.initialObjects = { + shapes: {}, + tracks: {}, + tags: {}, + }; + } - return result; - } + _getHash() { + const exported = this.collection.export(); + return JSON.stringify(exported); + } - async _put(data) { - const result = await this._request(data, 'put'); - return result; - } + async _request(data, action) { + const result = await serverProxy.annotations.updateAnnotations(this.sessionType, this.id, data, action); - async _create(created) { - const result = await this._request(created, 'create'); - return result; - } + return result; + } - async _update(updated) { - const result = await this._request(updated, 'update'); - return result; - } + async _put(data) { + const result = await this._request(data, 'put'); + return result; + } - async _delete(deleted) { - const result = await this._request(deleted, 'delete'); - return result; - } + async _create(created) { + const result = await this._request(created, 'create'); + return result; + } - _split(exported) { - const splitted = { - created: { - shapes: [], - tracks: [], - tags: [], - }, - updated: { - shapes: [], - tracks: [], - tags: [], - }, - deleted: { - shapes: [], - tracks: [], - tags: [], - }, - }; - - const keys = [ - 'id', - 'label_id', - 'group', - 'frame', - 'occluded', - 'z_order', - 'points', - 'rotation', - 'type', - 'shapes', - 'elements', - 'attributes', - 'value', - 'spec_id', - 'source', - 'outside', - ]; - - // Find created and updated objects - for (const type of Object.keys(exported)) { - for (const object of exported[type]) { - if (object.id in this.initialObjects[type]) { - const exportedHash = JSON.stringify(object, keys); - const initialHash = JSON.stringify(this.initialObjects[type][object.id], keys); - if (exportedHash !== initialHash) { - splitted.updated[type].push(object); - } - } else if (typeof object.id === 'undefined') { - splitted.created[type].push(object); - } else { - throw new ScriptingError( - `Id of object is defined "${object.id}" but it absents in initial state`, - ); - } - } - } + async _update(updated) { + const result = await this._request(updated, 'update'); + return result; + } - // Now find deleted objects - const indexes = { - shapes: exported.shapes.map((object) => +object.id), - tracks: exported.tracks.map((object) => +object.id), - tags: exported.tags.map((object) => +object.id), - }; + async _delete(deleted) { + const result = await this._request(deleted, 'delete'); + return result; + } - for (const type of Object.keys(this.initialObjects)) { - for (const id of Object.keys(this.initialObjects[type])) { - if (!indexes[type].includes(+id)) { - const object = this.initialObjects[type][id]; - splitted.deleted[type].push(object); + _split(exported) { + const splitted = { + created: { + shapes: [], + tracks: [], + tags: [], + }, + updated: { + shapes: [], + tracks: [], + tags: [], + }, + deleted: { + shapes: [], + tracks: [], + tags: [], + }, + }; + + const keys = [ + 'id', + 'label_id', + 'group', + 'frame', + 'occluded', + 'z_order', + 'points', + 'rotation', + 'type', + 'shapes', + 'elements', + 'attributes', + 'value', + 'spec_id', + 'source', + 'outside', + ]; + + // Find created and updated objects + for (const type of Object.keys(exported)) { + for (const object of exported[type]) { + if (object.id in this.initialObjects[type]) { + const exportedHash = JSON.stringify(object, keys); + const initialHash = JSON.stringify(this.initialObjects[type][object.id], keys); + if (exportedHash !== initialHash) { + splitted.updated[type].push(object); } + } else if (typeof object.id === 'undefined') { + splitted.created[type].push(object); + } else { + throw new ScriptingError( + `Id of object is defined "${object.id}" but it absents in initial state`, + ); } } - - return splitted; } - _updateCreatedObjects(saved, indexes) { - const savedLength = saved.tracks.length + saved.shapes.length + saved.tags.length; - const indexesLength = indexes.tracks.length + indexes.shapes.length + indexes.tags.length; - if (indexesLength !== savedLength) { - throw new ScriptingError( - `Number of indexes is differed by number of saved objects ${indexesLength} vs ${savedLength}`, - ); - } - - // Updated IDs of created objects - for (const type of Object.keys(indexes)) { - for (let i = 0; i < indexes[type].length; i++) { - const clientID = indexes[type][i]; - this.collection.objects[clientID].updateServerID(saved[type][i]); + // Now find deleted objects + const indexes = { + shapes: exported.shapes.map((object) => +object.id), + tracks: exported.tracks.map((object) => +object.id), + tags: exported.tags.map((object) => +object.id), + }; + + for (const type of Object.keys(this.initialObjects)) { + for (const id of Object.keys(this.initialObjects[type])) { + if (!indexes[type].includes(+id)) { + const object = this.initialObjects[type][id]; + splitted.deleted[type].push(object); } } } - _receiveIndexes(exported) { - // Receive client indexes before saving - const indexes = { - tracks: exported.tracks.map((track) => track.clientID), - shapes: exported.shapes.map((shape) => shape.clientID), - tags: exported.tags.map((tag) => tag.clientID), - }; - - // Remove them from the request body - exported.tracks - .concat(exported.shapes) - .concat(exported.tags) - .map((value) => { - delete value.clientID; - return value; - }); - - return indexes; + return splitted; + } + + _updateCreatedObjects(saved, indexes) { + const savedLength = saved.tracks.length + saved.shapes.length + saved.tags.length; + const indexesLength = indexes.tracks.length + indexes.shapes.length + indexes.tags.length; + if (indexesLength !== savedLength) { + throw new ScriptingError( + `Number of indexes is differed by number of saved objects ${indexesLength} vs ${savedLength}`, + ); } - async save(onUpdateArg) { - const onUpdate = typeof onUpdateArg === 'function' ? onUpdateArg : (message) => { - console.log(message); - }; - - const exported = this.collection.export(); - const { flush } = this.collection; - if (flush) { - onUpdate('Created objects are being saved on the server'); - const indexes = this._receiveIndexes(exported); - const savedData = await this._put({ ...exported, version: this.version }); - this.version = savedData.version; - this.collection.flush = false; - - this._updateCreatedObjects(savedData, indexes); - - this._resetState(); - for (const type of Object.keys(this.initialObjects)) { - for (const object of savedData[type]) { - this.initialObjects[type][object.id] = object; - } - } - } else { - const { created, updated, deleted } = this._split(exported); + // Updated IDs of created objects + for (const type of Object.keys(indexes)) { + for (let i = 0; i < indexes[type].length; i++) { + const clientID = indexes[type][i]; + this.collection.objects[clientID].updateServerID(saved[type][i]); + } + } + } + + _receiveIndexes(exported) { + // Receive client indexes before saving + const indexes = { + tracks: exported.tracks.map((track) => track.clientID), + shapes: exported.shapes.map((shape) => shape.clientID), + tags: exported.tags.map((tag) => tag.clientID), + }; + + // Remove them from the request body + exported.tracks + .concat(exported.shapes) + .concat(exported.tags) + .map((value) => { + delete value.clientID; + return value; + }); + + return indexes; + } - onUpdate('Created objects are being saved on the server'); - const indexes = this._receiveIndexes(created); - const createdData = await this._create({ ...created, version: this.version }); - this.version = createdData.version; + async save(onUpdateArg) { + const onUpdate = typeof onUpdateArg === 'function' ? onUpdateArg : (message) => { + console.log(message); + }; - this._updateCreatedObjects(createdData, indexes); + const exported = this.collection.export(); + const { flush } = this.collection; + if (flush) { + onUpdate('Created objects are being saved on the server'); + const indexes = this._receiveIndexes(exported); + const savedData = await this._put({ ...exported, version: this.version }); + this.version = savedData.version; + this.collection.flush = false; - for (const type of Object.keys(this.initialObjects)) { - for (const object of createdData[type]) { - this.initialObjects[type][object.id] = object; - } + this._updateCreatedObjects(savedData, indexes); + + this._resetState(); + for (const type of Object.keys(this.initialObjects)) { + for (const object of savedData[type]) { + this.initialObjects[type][object.id] = object; } + } + } else { + const { created, updated, deleted } = this._split(exported); - onUpdate('Updated objects are being saved on the server'); - this._receiveIndexes(updated); - const updatedData = await this._update({ ...updated, version: this.version }); - this.version = updatedData.version; + onUpdate('Created objects are being saved on the server'); + const indexes = this._receiveIndexes(created); + const createdData = await this._create({ ...created, version: this.version }); + this.version = createdData.version; - for (const type of Object.keys(this.initialObjects)) { - for (const object of updatedData[type]) { - this.initialObjects[type][object.id] = object; - } + this._updateCreatedObjects(createdData, indexes); + + for (const type of Object.keys(this.initialObjects)) { + for (const object of createdData[type]) { + this.initialObjects[type][object.id] = object; } + } - onUpdate('Deleted objects are being deleted from the server'); - this._receiveIndexes(deleted); - const deletedData = await this._delete({ ...deleted, version: this.version }); - this._version = deletedData.version; + onUpdate('Updated objects are being saved on the server'); + this._receiveIndexes(updated); + const updatedData = await this._update({ ...updated, version: this.version }); + this.version = updatedData.version; - for (const type of Object.keys(this.initialObjects)) { - for (const object of deletedData[type]) { - delete this.initialObjects[type][object.id]; - } + for (const type of Object.keys(this.initialObjects)) { + for (const object of updatedData[type]) { + this.initialObjects[type][object.id] = object; } } - this.hash = this._getHash(); - } + onUpdate('Deleted objects are being deleted from the server'); + this._receiveIndexes(deleted); + const deletedData = await this._delete({ ...deleted, version: this.version }); + this.version = deletedData.version; - hasUnsavedChanges() { - return this._getHash() !== this.hash; + for (const type of Object.keys(this.initialObjects)) { + for (const object of deletedData[type]) { + delete this.initialObjects[type][object.id]; + } + } } + + this.hash = this._getHash(); } - module.exports = AnnotationsSaver; -})(); + hasUnsavedChanges(): boolean { + return this._getHash() !== this.hash; + } +} diff --git a/cvat-core/src/annotations.ts b/cvat-core/src/annotations.ts index de14ca99..4bcd05d0 100644 --- a/cvat-core/src/annotations.ts +++ b/cvat-core/src/annotations.ts @@ -4,16 +4,15 @@ // SPDX-License-Identifier: MIT import { Storage } from './storage'; - -const serverProxy = require('./server-proxy').default; -const Collection = require('./annotations-collection'); -const AnnotationsSaver = require('./annotations-saver'); -const AnnotationsHistory = require('./annotations-history').default; -const { checkObjectType } = require('./common'); -const Project = require('./project').default; -const { Task, Job } = require('./session'); -const { ScriptingError, DataError, ArgumentError } = require('./exceptions'); -const { getDeletedFrames } = require('./frames'); +import serverProxy from './server-proxy'; +import Collection from './annotations-collection'; +import AnnotationsSaver from './annotations-saver'; +import AnnotationsHistory from './annotations-history'; +import { checkObjectType } from './common'; +import Project from './project'; +import { Task, Job } from './session'; +import { ScriptingError, DataError, ArgumentError } from './exceptions'; +import { getDeletedFrames } from './frames'; const jobCache = new WeakMap(); const taskCache = new WeakMap(); diff --git a/cvat-core/src/api.ts b/cvat-core/src/api.ts index 92fba355..3b3dfedc 100644 --- a/cvat-core/src/api.ts +++ b/cvat-core/src/api.ts @@ -1,5 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -39,103 +39,24 @@ import config from './config'; import implementAPI from './api-implementation'; function build() { - /** - * API entrypoint - * @namespace cvat - * @memberof module:API - */ const cvat = { - /** - * Namespace is used for an interaction with a server - * @namespace server - * @package - * @memberof module:API.cvat - */ server: { - /** - * @typedef {Object} ServerInfo - * @property {string} name A name of the tool - * @property {string} description A description of the tool - * @property {string} version A version of the tool - * @global - */ - - /** - * Method returns some information about the annotation tool - * @method about - * @async - * @memberof module:API.cvat.server - * @return {ServerInfo} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async about() { const result = await PluginRegistry.apiWrapper(cvat.server.about); return result; }, - /** - * @typedef {Object} FileInfo - * @property {string} name A name of a file - * @property {module:API.cvat.enums.ShareFileType} type - * A type of a file - * @global - */ - - /** - * Method returns a list of files in a specified directory on a share - * @method share - * @async - * @memberof module:API.cvat.server - * @param {string} [directory=/] - Share directory path - * @returns {FileInfo[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async share(directory = '/') { const result = await PluginRegistry.apiWrapper(cvat.server.share, directory); return result; }, - /** - * Method returns available annotation formats - * @method formats - * @async - * @memberof module:API.cvat.server - * @returns {module:API.cvat.classes.AnnotationFormats} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async formats() { const result = await PluginRegistry.apiWrapper(cvat.server.formats); return result; }, - /** - * Method returns user agreements that the user must accept - * @method userAgreements - * @async - * @memberof module:API.cvat.server - * @returns {Object[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async userAgreements() { const result = await PluginRegistry.apiWrapper(cvat.server.userAgreements); return result; }, - /** - * Method allows to register on a server - * @method register - * @async - * @memberof module:API.cvat.server - * @param {string} username An username for the new account - * @param {string} firstName A first name for the new account - * @param {string} lastName A last name for the new account - * @param {string} email A email address for the new account - * @param {string} password A password for the new account - * @param {Object} userConfirmations An user confirmations of terms of use if needed - * @returns {Object} response data - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async register(username, firstName, lastName, email, password, userConfirmations) { const result = await PluginRegistry.apiWrapper( cvat.server.register, @@ -148,28 +69,10 @@ function build() { ); return result; }, - /** - * Method allows to login on a server - * @method login - * @async - * @memberof module:API.cvat.server - * @param {string} username An username of an account - * @param {string} password A password of an account - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async login(username, password) { const result = await PluginRegistry.apiWrapper(cvat.server.login, username, password); return result; }, - /** - * Method allows to logout from the server - * @method logout - * @async - * @memberof module:API.cvat.server - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async logout() { const result = await PluginRegistry.apiWrapper(cvat.server.logout); return result; @@ -178,17 +81,6 @@ function build() { const result = await PluginRegistry.apiWrapper(cvat.server.advancedAuthentication); return result; }, - /** - * Method allows to change user password - * @method changePassword - * @async - * @memberof module:API.cvat.server - * @param {string} oldPassword Current password for the account - * @param {string} newPassword1 New password for the account - * @param {string} newPassword2 Confirmation password for the account - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async changePassword(oldPassword, newPassword1, newPassword2) { const result = await PluginRegistry.apiWrapper( cvat.server.changePassword, @@ -198,31 +90,10 @@ function build() { ); return result; }, - /** - * Method allows to reset user password - * @method requestPasswordReset - * @async - * @memberof module:API.cvat.server - * @param {string} email A email address for the account - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async requestPasswordReset(email) { const result = await PluginRegistry.apiWrapper(cvat.server.requestPasswordReset, email); return result; }, - /** - * Method allows to confirm reset user password - * @method resetPassword - * @async - * @memberof module:API.cvat.server - * @param {string} newPassword1 New password for the account - * @param {string} newPassword2 Confirmation password for the account - * @param {string} uid User id - * @param {string} token Request authentication token - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async resetPassword(newPassword1, newPassword2, uid, token) { const result = await PluginRegistry.apiWrapper( cvat.server.resetPassword, @@ -233,29 +104,10 @@ function build() { ); return result; }, - /** - * Method allows to know whether you are authorized on the server - * @method authorized - * @async - * @memberof module:API.cvat.server - * @returns {boolean} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async authorized() { const result = await PluginRegistry.apiWrapper(cvat.server.authorized); return result; }, - /** - * Method allows to health check the server - * @method healthCheck - * @async - * @memberof module:API.cvat.server - * @param {number} requestTimeout - * @returns {Object | undefined} response data if exist - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async healthCheck(maxRetries = 1, checkPeriod = 3000, requestTimeout = 5000, progressCallback = undefined) { const result = await PluginRegistry.apiWrapper( cvat.server.healthCheck, @@ -266,31 +118,10 @@ function build() { ); return result; }, - /** - * Method allows to do requests via cvat-core with authorization headers - * @method request - * @async - * @memberof module:API.cvat.server - * @param {string} url - * @param {Object} data request parameters: method, headers, data, etc. - * @returns {Object | undefined} response data if exist - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async request(url, data) { const result = await PluginRegistry.apiWrapper(cvat.server.request, url, data); return result; }, - - /** - * Method returns apps that are installed on the server - * @method installedApps - * @async - * @memberof module:API.cvat.server - * @returns {Object} map {installedApp: boolean} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async installedApps() { const result = await PluginRegistry.apiWrapper(cvat.server.installedApps); return result; @@ -308,442 +139,72 @@ function build() { return result; }, }, - /** - * Namespace is used for getting projects - * @namespace projects - * @memberof module:API.cvat - */ projects: { - /** - * @typedef {Object} ProjectFilter - * @property {string} name Check if name contains this value - * @property {module:API.cvat.enums.ProjectStatus} status - * Check if status contains this value - * @property {number} id Check if id equals this value - * @property {number} page Get specific page - * (default REST API returns 20 projects per request. - * In order to get more, it is need to specify next page) - * @property {string} owner Check if owner user contains this value - * @property {string} search Combined search of contains among all fields - * @global - */ - - /** - * Method returns list of projects corresponding to a filter - * @method get - * @async - * @memberof module:API.cvat.projects - * @param {ProjectFilter} [filter={}] project filter - * @returns {module:API.cvat.classes.Project[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async get(filter = {}) { const result = await PluginRegistry.apiWrapper(cvat.projects.get, filter); return result; }, - - /** - * Method returns list of project names with project ids - * corresponding to a search phrase - * used for autocomplete field - * @method searchNames - * @async - * @memberof module:API.cvat.projects - * @param {string} [search = ''] search phrase - * @param {number} [limit = 10] number of returning project names - * @returns {module:API.cvat.classes.Project[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * - */ async searchNames(search = '', limit = 10) { const result = await PluginRegistry.apiWrapper(cvat.projects.searchNames, search, limit); return result; }, }, - /** - * Namespace is used for getting tasks - * @namespace tasks - * @memberof module:API.cvat - */ tasks: { - /** - * @typedef {Object} TaskFilter - * @property {string} name Check if name contains this value - * @property {module:API.cvat.enums.TaskStatus} status - * Check if status contains this value - * @property {module:API.cvat.enums.TaskMode} mode - * Check if mode contains this value - * @property {number} id Check if id equals this value - * @property {number} page Get specific page - * (default REST API returns 20 tasks per request. - * In order to get more, it is need to specify next page) - * @property {number} projectId Check if project_id field contains this value - * @property {string} owner Check if owner user contains this value - * @property {string} assignee Check if assigneed contains this value - * @property {string} search Combined search of contains among all fields - * @global - */ - - /** - * Method returns list of tasks corresponding to a filter - * @method get - * @async - * @memberof module:API.cvat.tasks - * @param {TaskFilter} [filter={}] task filter - * @returns {module:API.cvat.classes.Task[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async get(filter = {}) { const result = await PluginRegistry.apiWrapper(cvat.tasks.get, filter); return result; }, }, - /** - * Namespace is used for getting jobs - * @namespace jobs - * @memberof module:API.cvat - */ jobs: { - /** - * @typedef {Object} JobFilter - * Only one of fields is allowed simultaneously - * @property {number} taskID filter all jobs of specific task - * @property {number} jobID filter job with a specific id - * @global - */ - - /** - * Method returns list of jobs corresponding to a filter - * @method get - * @async - * @memberof module:API.cvat.jobs - * @param {JobFilter} filter job filter - * @returns {module:API.cvat.classes.Job[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async get(filter = {}) { const result = await PluginRegistry.apiWrapper(cvat.jobs.get, filter); return result; }, }, - /** - * Namespace is used for getting users - * @namespace users - * @memberof module:API.cvat - */ users: { - /** - * @typedef {Object} UserFilter - * @property {boolean} self get only self - * @global - */ - - /** - * Method returns list of users corresponding to a filter - * @method get - * @async - * @memberof module:API.cvat.users - * @param {UserFilter} [filter={}] user filter - * @returns {module:API.cvat.classes.User[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async get(filter = {}) { const result = await PluginRegistry.apiWrapper(cvat.users.get, filter); return result; }, }, - /** - * Namespace is used for plugin management - * @namespace plugins - * @memberof module:API.cvat - */ plugins: { - /** - * @typedef {Object} Plugin - * A plugin is a Javascript object. It must have properties are listed below.
- * It also mustn't have property 'functions' which is used internally.
- * You can expand any API method including class methods.
- * In order to expand class method just use a class name - * in a cvat space (example is listed below). - * - * @property {string} name A name of a plugin - * @property {string} description A description of a plugin - * Example plugin implementation listed below: - * @example - * plugin = { - * name: 'Example Plugin', - * description: 'This example plugin demonstrates how plugin system in CVAT works', - * cvat: { - * server: { - * about: { - * // Plugin adds some actions after executing the cvat.server.about() - * // For example it adds a field with installed plugins to a result - * // An argument "self" is a plugin itself - * // An argument "result" is a return value of cvat.server.about() - * // All next arguments are arguments of a wrapped function - * // (in this case the wrapped function doesn't have any arguments) - * async leave(self, result) { - * result.plugins = await self.internal.getPlugins(); - * // Note that a method leave must return "result" (changed or not) - * // Otherwise API won't work as expected - * return result; - * }, - * }, - * }, - * // In this example plugin also wraps a class method - * classes: { - * Job: { - * prototype: { - * annotations: { - * put: { - * // The first argument "self" is a plugin, like in a case above - * // The second argument is an argument of the - * // Job.annotations.put() - * // It contains an array of objects to put - * // In this sample we round objects coordinates and save them - * enter(self, objects) { - * for (const obj of objects) { - * if (obj.type != 'tag') { - * const points = obj.position.map((point) => { - * const roundPoint = { - * x: Math.round(point.x), - * y: Math.round(point.y), - * }; - * return roundPoint; - * }); - * } - * } - * }, - * }, - * }, - * }, - * }, - * }, - * }, - * // In general you can add any others members to your plugin - * // Members below are only examples - * internal: { - * async getPlugins() { - * // Collect information about installed plugins - * const plugins = await cvat.plugins.list(); - * return plugins.map((el) => { - * return { - * name: el.name, - * description: el.description, - * }; - * }); - * }, - * }, - * }; - * @global - */ - - /** - * Method returns list of installed plugins - * @method list - * @async - * @memberof module:API.cvat.plugins - * @returns {Plugin[]} - * @throws {module:API.cvat.exceptions.PluginError} - */ async list() { const result = await PluginRegistry.apiWrapper(cvat.plugins.list); return result; }, - /** - * Install plugin to CVAT - * @method register - * @async - * @memberof module:API.cvat.plugins - * @param {Plugin} [plugin] plugin for registration - * @throws {module:API.cvat.exceptions.PluginError} - */ async register(plugin) { const result = await PluginRegistry.apiWrapper(cvat.plugins.register, plugin); return result; }, }, - - /** - * Namespace is used for serverless functions management (mainly related with DL models) - * @namespace lambda - * @memberof module:API.cvat - */ lambda: { - /** - * Method returns list of available serverless models - * @method list - * @async - * @memberof module:API.cvat.lambda - * @returns {module:API.cvat.classes.MLModel[]} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async list() { const result = await PluginRegistry.apiWrapper(cvat.lambda.list); return result; }, - - /** - * Run long-time request for a function on a specific task - * @method run - * @async - * @memberof module:API.cvat.lambda - * @param {module:API.cvat.classes.Task} task task to be annotated - * @param {module:API.cvat.classes.MLModel} model model used to get annotation - * @param {object} [args] extra arguments - * @returns {string} requestID - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ async run(task, model, args) { const result = await PluginRegistry.apiWrapper(cvat.lambda.run, task, model, args); return result; }, - - /** - * Run short-time request for a function on a specific task - * @method call - * @async - * @memberof module:API.cvat.lambda - * @param {module:API.cvat.classes.Task} task task to be annotated - * @param {module:API.cvat.classes.MLModel} model model used to get annotation - * @param {object} [args] extra arguments - * @returns {object[]} annotations - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ async call(task, model, args) { const result = await PluginRegistry.apiWrapper(cvat.lambda.call, task, model, args); return result; }, - - /** - * Cancel running of a serverless function for a specific task - * @method cancel - * @async - * @memberof module:API.cvat.lambda - * @param {string} requestID - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ async cancel(requestID) { const result = await PluginRegistry.apiWrapper(cvat.lambda.cancel, requestID); return result; }, - - /** - * @callback onRequestStatusChange - * @param {string} status - * @param {number} progress - * @param {string} [message] - * @global - */ - /** - * Listen for a specific request - * @method listen - * @async - * @memberof module:API.cvat.lambda - * @param {string} requestID - * @param {onRequestStatusChange} onChange - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async listen(requestID, onChange) { const result = await PluginRegistry.apiWrapper(cvat.lambda.listen, requestID, onChange); return result; }, - - /** - * Get active lambda requests - * @method requests - * @async - * @memberof module:API.cvat.lambda - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async requests() { const result = await PluginRegistry.apiWrapper(cvat.lambda.requests); return result; }, }, - /** - * Namespace to working with logs - * @namespace logger - * @memberof module:API.cvat - */ - /** - * Method to logger configuration - * @method configure - * @memberof module:API.cvat.logger - * @param {function} isActiveChecker - callback to know if logger - * should increase working time or not - * @param {object} userActivityCallback - container for a callback
- * Logger put here a callback to update user activity timer
- * You can call it outside - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - - /** - * Append log to a log collection
- * Durable logs will have been added after "close" method is called for them
- * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
- * Payload of ignored logs are shallowly combined to previous logs of the same type - * @method log - * @memberof module:API.cvat.logger - * @param {module:API.cvat.enums.LogType | string} type - log type - * @param {Object} [payload = {}] - any other data that will be appended to the log - * @param {boolean} [wait = false] - specifies if log is durable - * @returns {module:API.cvat.classes.Log} - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - - /** - * Save accumulated logs on a server - * @method save - * @memberof module:API.cvat.logger - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @instance - * @async - */ logger: loggerStorage, - /** - * Namespace contains some changeable configurations - * @namespace config - * @memberof module:API.cvat - */ config: { - /** - * @memberof module:API.cvat.config - * @property {string} backendAPI host with a backend api - * @memberof module:API.cvat.config - * @property {string} proxy Axios proxy settings. - * For more details please read here - * @memberof module:API.cvat.config - * @property {string} origin ui URL origin - * @memberof module:API.cvat.config - * @property {number} uploadChunkSize max size of one data request in mb - * @memberof module:API.cvat.config - * @property {number} removeUnderlyingMaskPixels defines if after adding/changing - * a mask it should remove overlapped pixels from other objects - * @memberof module:API.cvat.config - */ get backendAPI() { return config.backendAPI; }, @@ -775,35 +236,10 @@ function build() { config.removeUnderlyingMaskPixels = value; }, }, - /** - * Namespace contains some library information e.g. api version - * @namespace client - * @memberof module:API.cvat - */ client: { - /** - * @property {string} version Client version. - * Format: {major}.{minor}.{patch} - *
  • A major number is changed after an API becomes - * incompatible with a previous version - *
  • A minor number is changed after an API expands - *
  • A patch number is changed after an each build - * @memberof module:API.cvat.client - * @readonly - */ version: `${pjson.version}`, }, - /** - * Namespace is used for access to enums - * @namespace enums - * @memberof module:API.cvat - */ enums, - /** - * Namespace is used for access to exceptions - * @namespace exceptions - * @memberof module:API.cvat - */ exceptions: { Exception, ArgumentError, @@ -812,110 +248,32 @@ function build() { PluginError, ServerError, }, - /** - * Namespace is used for getting cloud storages - * @namespace cloudStorages - * @memberof module:API.cvat - */ cloudStorages: { - /** - * @typedef {Object} CloudStorageFilter - * @property {string} displayName Check if displayName contains this value - * @property {string} resource Check if resource name contains this value - * @property {module:API.cvat.enums.ProviderType} providerType Check if providerType equal this value - * @property {number} id Check if id equals this value - * @property {number} page Get specific page - * (default REST API returns 20 clouds storages per request. - * In order to get more, it is need to specify next page) - * @property {string} owner Check if an owner name contains this value - * @property {string} search Combined search of contains among all the fields - * @global - */ - - /** - * Method returns a list of cloud storages corresponding to a filter - * @method get - * @async - * @memberof module:API.cvat.cloudStorages - * @param {CloudStorageFilter} [filter={}] cloud storage filter - * @returns {module:API.cvat.classes.CloudStorage[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async get(filter = {}) { const result = await PluginRegistry.apiWrapper(cvat.cloudStorages.get, filter); return result; }, }, - /** - * This namespace could be used to get organizations list from the server - * @namespace organizations - * @memberof module:API.cvat - */ organizations: { - /** - * Method returns a list of organizations - * @method get - * @async - * @memberof module:API.cvat.organizations - * @returns {module:API.cvat.classes.Organization[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async get() { const result = await PluginRegistry.apiWrapper(cvat.organizations.get); return result; }, - /** - * Method activates organization context - * @method activate - * @async - * @param {module:API.cvat.classes.Organization} - * @memberof module:API.cvat.organizations - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async activate(organization) { const result = await PluginRegistry.apiWrapper(cvat.organizations.activate, organization); return result; }, - /** - * Method deactivates organization context - * @method deactivate - * @async - * @memberof module:API.cvat.organizations - * @throws {module:API.cvat.exceptions.PluginError} - */ async deactivate() { const result = await PluginRegistry.apiWrapper(cvat.organizations.deactivate); return result; }, }, - /** - * This namespace could be used to get webhooks list from the server - * @namespace webhooks - * @memberof module:API.cvat - */ webhooks: { - /** - * Method returns a list of organizations - * @method get - * @async - * @memberof module:API.cvat.webhooks - * @returns {module:API.cvat.classes.Webhook[]} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - */ async get(filter: any) { const result = await PluginRegistry.apiWrapper(cvat.webhooks.get, filter); return result; }, }, - /** - * Namespace is used for access to classes - * @namespace classes - * @memberof module:API.cvat - */ classes: { User, Project: implementProject(Project), diff --git a/cvat-core/src/enums.ts b/cvat-core/src/enums.ts index 9d20f28a..1990dc1a 100644 --- a/cvat-core/src/enums.ts +++ b/cvat-core/src/enums.ts @@ -3,63 +3,23 @@ // // SPDX-License-Identifier = MIT -/** - * Share files types - * @enum {string} - * @name ShareFileType - * @memberof module:API.cvat.enums - * @property {string} DIR 'DIR' - * @property {string} REG 'REG' - * @readonly -*/ export enum ShareFileType { DIR = 'DIR', REG = 'REG', } -/** - * Task statuses - * @enum {string} - * @name TaskStatus - * @memberof module:API.cvat.enums - * @property {string} ANNOTATION 'annotation' - * @property {string} VALIDATION 'validation' - * @property {string} COMPLETED 'completed' - * @readonly -*/ export enum TaskStatus { ANNOTATION = 'annotation', VALIDATION = 'validation', COMPLETED = 'completed', } -/** - * Job stages - * @enum {string} - * @name JobStage - * @memberof module:API.cvat.enums - * @property {string} ANNOTATION 'annotation' - * @property {string} VALIDATION 'validation' - * @property {string} ACCEPTANCE 'acceptance' - * @readonly -*/ export enum JobStage { ANNOTATION = 'annotation', VALIDATION = 'validation', ACCEPTANCE = 'acceptance', } -/** - * Job states - * @enum {string} - * @name JobState - * @memberof module:API.cvat.enums - * @property {string} NEW 'new' - * @property {string} IN_PROGRESS 'in progress' - * @property {string} COMPLETED 'completed' - * @property {string} REJECTED 'rejected' - * @readonly -*/ export enum JobState { NEW = 'new', IN_PROGRESS = 'in progress', @@ -67,32 +27,11 @@ export enum JobState { REJECTED = 'rejected', } -/** - * Task dimension - * @enum - * @name DimensionType - * @memberof module:API.cvat.enums - * @property {string} DIMENSION_2D '2d' - * @property {string} DIMENSION_3D '3d' - * @readonly -*/ export enum DimensionType { DIMENSION_2D = '2d', DIMENSION_3D = '3d', } -/** - * List of RQ statuses - * @enum {string} - * @name RQStatus - * @memberof module:API.cvat.enums - * @property {string} QUEUED 'queued' - * @property {string} STARTED 'started' - * @property {string} FINISHED 'finished' - * @property {string} FAILED 'failed' - * @property {string} UNKNOWN 'unknown' - * @readonly -*/ export enum RQStatus { QUEUED = 'queued', STARTED = 'started', @@ -101,32 +40,11 @@ export enum RQStatus { UNKNOWN = 'unknown', } -/** - * Task modes - * @enum {string} - * @name TaskMode - * @memberof module:API.cvat.enums - * @property {string} ANNOTATION 'annotation' - * @property {string} INTERPOLATION 'interpolation' - * @readonly -*/ export enum TaskMode { ANNOTATION = 'annotation', INTERPOLATION = 'interpolation', } -/** - * Attribute types - * @enum {string} - * @name AttributeType - * @memberof module:API.cvat.enums - * @property {string} CHECKBOX 'checkbox' - * @property {string} SELECT 'select' - * @property {string} RADIO 'radio' - * @property {string} NUMBER 'number' - * @property {string} TEXT 'text' - * @readonly -*/ export enum AttributeType { CHECKBOX = 'checkbox', RADIO = 'radio', @@ -135,35 +53,12 @@ export enum AttributeType { TEXT = 'text', } -/** - * Object types - * @enum {string} - * @name ObjectType - * @memberof module:API.cvat.enums - * @property {string} TAG 'tag' - * @property {string} SHAPE 'shape' - * @property {string} TRACK 'track' - * @readonly -*/ export enum ObjectType { TAG = 'tag', SHAPE = 'shape', TRACK = 'track', } -/** - * Object shapes - * @enum {string} - * @name ShapeType - * @memberof module:API.cvat.enums - * @property {string} RECTANGLE 'rectangle' - * @property {string} POLYGON 'polygon' - * @property {string} POLYLINE 'polyline' - * @property {string} POINTS 'points' - * @property {string} CUBOID 'cuboid' - * @property {string} SKELETON 'skeleton' - * @readonly -*/ export enum ShapeType { RECTANGLE = 'rectangle', POLYGON = 'polygon', @@ -175,54 +70,11 @@ export enum ShapeType { MASK = 'mask', } -/** - * Annotation type - * @enum {string} - * @name Source - * @memberof module:API.cvat.enums - * @property {string} MANUAL 'manual' - * @property {string} AUTO 'auto' - * @readonly -*/ export enum Source { MANUAL = 'manual', AUTO = 'auto', } -/** - * Logger event types - * @enum {string} - * @name LogType - * @memberof module:API.cvat.enums - * @property {string} loadJob Load job - * @property {string} saveJob Save job - * @property {string} restoreJob Restore job - * @property {string} uploadAnnotations Upload annotations - * @property {string} sendUserActivity Send user activity - * @property {string} sendException Send exception - * @property {string} sendTaskInfo Send task info - * @property {string} drawObject Draw object - * @property {string} pasteObject Paste object - * @property {string} copyObject Copy object - * @property {string} propagateObject Propagate object - * @property {string} dragObject Drag object - * @property {string} resizeObject Resize object - * @property {string} deleteObject Delete object - * @property {string} lockObject Lock object - * @property {string} mergeObjects Merge objects - * @property {string} changeAttribute Change attribute - * @property {string} changeLabel Change label - * @property {string} changeFrame Change frame - * @property {string} moveImage Move image - * @property {string} zoomImage Zoom image - * @property {string} fitImage Fit image - * @property {string} rotateImage Rotate image - * @property {string} undoAction Undo action - * @property {string} redoAction Redo action - * @property {string} pressShortcut Press shortcut - * @property {string} debugInfo Debug info - * @readonly -*/ export enum LogType { loadJob = 'Load job', saveJob = 'Save job', @@ -257,30 +109,6 @@ export enum LogType { debugInfo = 'Debug info', } -/** - * Types of actions with annotations - * @enum {string} - * @name HistoryActions - * @memberof module:API.cvat.enums - * @property {string} CHANGED_LABEL Changed label - * @property {string} CHANGED_ATTRIBUTES Changed attributes - * @property {string} CHANGED_POINTS Changed points - * @property {string} CHANGED_OUTSIDE Changed outside - * @property {string} CHANGED_OCCLUDED Changed occluded - * @property {string} CHANGED_ZORDER Changed z-order - * @property {string} CHANGED_LOCK Changed lock - * @property {string} CHANGED_COLOR Changed color - * @property {string} CHANGED_HIDDEN Changed hidden - * @property {string} CHANGED_SOURCE Changed source - * @property {string} MERGED_OBJECTS Merged objects - * @property {string} SPLITTED_TRACK Splitted track - * @property {string} GROUPED_OBJECTS Grouped objects - * @property {string} CREATED_OBJECTS Created objects - * @property {string} REMOVED_OBJECT Removed object - * @property {string} REMOVED_FRAME Removed frame - * @property {string} RESTORED_FRAME Restored frame - * @readonly -*/ export enum HistoryActions { CHANGED_LABEL = 'Changed label', CHANGED_ATTRIBUTES = 'Changed attributes', @@ -304,25 +132,12 @@ export enum HistoryActions { RESTORED_FRAME = 'Restored frame', } -/** - * Enum string values. - * @name ModelType - * @memberof module:API.cvat.enums - * @enum {string} -*/ export enum ModelType { DETECTOR = 'detector', INTERACTOR = 'interactor', TRACKER = 'tracker', } -/** - * Array of hex colors - * @name colors - * @memberof module:API.cvat.enums - * @type {string[]} - * @readonly -*/ export const colors = [ '#33ddff', '#fa3253', @@ -356,33 +171,12 @@ export const colors = [ '#733380', ]; -/** - * Types of cloud storage providers - * @enum {string} - * @name CloudStorageProviderType - * @memberof module:API.cvat.enums - * @property {string} AWS_S3 'AWS_S3_BUCKET' - * @property {string} AZURE 'AZURE_CONTAINER' - * @property {string} GOOGLE_CLOUD_STORAGE 'GOOGLE_CLOUD_STORAGE' - * @readonly -*/ export enum CloudStorageProviderType { AWS_S3_BUCKET = 'AWS_S3_BUCKET', AZURE_CONTAINER = 'AZURE_CONTAINER', GOOGLE_CLOUD_STORAGE = 'GOOGLE_CLOUD_STORAGE', } -/** - * Types of cloud storage credentials - * @enum {string} - * @name CloudStorageCredentialsType - * @memberof module:API.cvat.enums - * @property {string} KEY_SECRET_KEY_PAIR 'KEY_SECRET_KEY_PAIR' - * @property {string} ACCOUNT_NAME_TOKEN_PAIR 'ACCOUNT_NAME_TOKEN_PAIR' - * @property {string} ANONYMOUS_ACCESS 'ANONYMOUS_ACCESS' - * @property {string} KEY_FILE_PATH 'KEY_FILE_PATH' - * @readonly - */ export enum CloudStorageCredentialsType { KEY_SECRET_KEY_PAIR = 'KEY_SECRET_KEY_PAIR', ACCOUNT_NAME_TOKEN_PAIR = 'ACCOUNT_NAME_TOKEN_PAIR', @@ -390,33 +184,12 @@ export enum CloudStorageCredentialsType { KEY_FILE_PATH = 'KEY_FILE_PATH', } -/** - * Types of cloud storage statuses - * @enum {string} - * @name CloudStorageStatus - * @memberof module:API.cvat.enums - * @property {string} AVAILABLE 'AVAILABLE' - * @property {string} NOT_FOUND 'NOT_FOUND' - * @property {string} FORBIDDEN 'FORBIDDEN' - * @readonly - */ export enum CloudStorageStatus { AVAILABLE = 'AVAILABLE', NOT_FOUND = 'NOT_FOUND', FORBIDDEN = 'FORBIDDEN', } -/** - * Membership roles - * @enum {string} - * @name MembershipRole - * @memberof module:API.cvat.enums - * @property {string} WORKER 'worker' - * @property {string} SUPERVISOR 'supervisor' - * @property {string} MAINTAINER 'maintainer' - * @property {string} OWNER 'owner' - * @readonly -*/ export enum MembershipRole { WORKER = 'worker', SUPERVISOR = 'supervisor', @@ -424,17 +197,6 @@ export enum MembershipRole { OWNER = 'owner', } -/** - * Sorting methods - * @enum {string} - * @name SortingMethod - * @memberof module:API.cvat.enums - * @property {string} LEXICOGRAPHICAL 'lexicographical' - * @property {string} NATURAL 'natural' - * @property {string} PREDEFINED 'predefined' - * @property {string} RANDOM 'random' - * @readonly -*/ export enum SortingMethod { LEXICOGRAPHICAL = 'lexicographical', NATURAL = 'natural', @@ -442,42 +204,16 @@ export enum SortingMethod { RANDOM = 'random', } -/** - * Types of storage locations - * @enum {string} - * @name StorageLocation - * @memberof module:API.cvat.enums - * @property {string} LOCAL 'local' - * @property {string} CLOUD_STORAGE 'cloud_storage' - * @readonly -*/ export enum StorageLocation { LOCAL = 'local', CLOUD_STORAGE = 'cloud_storage', } -/** - * Webhook source types - * @enum {string} - * @name WebhookSourceType - * @memberof module:API.cvat.enums - * @property {string} ORGANIZATION 'organization' - * @property {string} PROJECT 'project' - * @readonly -*/ export enum WebhookSourceType { ORGANIZATION = 'organization', PROJECT = 'project', } -/** - * Webhook content types - * @enum {string} - * @name WebhookContentType - * @memberof module:API.cvat.enums - * @property {string} JSON 'json' - * @readonly -*/ export enum WebhookContentType { JSON = 'application/json', } diff --git a/cvat-core/src/exceptions.ts b/cvat-core/src/exceptions.ts index ba40b0a8..c1b1d34c 100644 --- a/cvat-core/src/exceptions.ts +++ b/cvat-core/src/exceptions.ts @@ -6,12 +6,6 @@ import Platform from 'platform'; import ErrorStackParser from 'error-stack-parser'; -/** - * Base exception class - * @memberof module:API.cvat.exceptions - * @extends Error - * @ignore - */ export class Exception extends Error { private readonly time: string; private readonly system: string; @@ -21,9 +15,6 @@ export class Exception extends Error { private readonly line: number; private readonly column: number; - /** - * @param {string} message - Exception message - */ constructor(message) { super(message); const time = new Date().toISOString(); @@ -73,43 +64,15 @@ export class Exception extends Error { get: () => time, }, // jobID: { - // /** - // * @name jobID - // * @type {number} - // * @memberof module:API.cvat.exceptions.Exception - // * @readonly - // * @instance - // */ // get: () => jobID, // }, // taskID: { - // /** - // * @name taskID - // * @type {number} - // * @memberof module:API.cvat.exceptions.Exception - // * @readonly - // * @instance - // */ // get: () => taskID, // }, // projID: { - // /** - // * @name projID - // * @type {number} - // * @memberof module:API.cvat.exceptions.Exception - // * @readonly - // * @instance - // */ // get: () => projID, // }, // clientID: { - // /** - // * @name clientID - // * @type {number} - // * @memberof module:API.cvat.exceptions.Exception - // * @readonly - // * @instance - // */ // get: () => clientID, // }, filename: { @@ -146,14 +109,6 @@ export class Exception extends Error { ); } - /** - * Save an exception on a server - * @name save - * @method - * @memberof Exception - * @instance - * @async - */ async save(): Promise { const exceptionObject = { system: this.system, @@ -171,6 +126,7 @@ export class Exception extends Error { }; try { + // eslint-disable-next-line @typescript-eslint/no-var-requires const serverProxy = require('./server-proxy').default; await serverProxy.server.exception(exceptionObject); } catch (exception) { @@ -179,73 +135,21 @@ export class Exception extends Error { } } -/** - * Exceptions are referred with arguments data - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ -export class ArgumentError extends Exception { - /** - * @param {string} message - Exception message - */ -} +export class ArgumentError extends Exception {} -/** - * Unexpected problems with data which are not connected with a user input - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ -export class DataError extends Exception { - /** - * @param {string} message - Exception message - */ -} +export class DataError extends Exception {} -/** - * Unexpected situations in code - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ -export class ScriptingError extends Exception { - /** - * @param {string} message - Exception message - */ -} +export class ScriptingError extends Exception {} -/** - * Plugin-referred exceptions - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ -export class PluginError extends Exception { - /** - * @param {string} message - Exception message - */ -} +export class PluginError extends Exception {} -/** - * Exceptions in interaction with a server - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ export class ServerError extends Exception { - /** - * @param {string} message - Exception message - * @param {(string|number)} code - Response code - */ constructor(message, code) { super(message); Object.defineProperties( this, Object.freeze({ - /** - * @name code - * @type {(string|number)} - * @memberof module:API.cvat.exceptions.ServerError - * @readonly - * @instance - */ code: { get: () => code, }, diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 6e0d3961..f97b6d5c 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -1,5 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -12,11 +12,6 @@ import { Exception, ArgumentError, DataError } from './exceptions'; // This is the frames storage const frameDataCache = {}; -/** - * Class provides meta information about specific frame and frame itself - * @memberof module:API.cvat.classes - * @hideconstructor - */ export class FrameData { constructor({ width, @@ -33,93 +28,34 @@ export class FrameData { Object.defineProperties( this, Object.freeze({ - /** - * @name filename - * @type {string} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ filename: { value: name, writable: false, }, - /** - * @name width - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ width: { value: width, writable: false, }, - /** - * @name height - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ height: { value: height, writable: false, }, - /** - * @name jid - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ jid: { value: jobID, writable: false, }, - /** - * @name number - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ number: { value: frameNumber, writable: false, }, - /** - * True if some context images are associated with this frame - * @name hasRelatedContext - * @type {boolean} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ hasRelatedContext: { value: hasRelatedContext, writable: false, }, - /** - * Start frame of the frame in the job - * @name startFrame - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ startFrame: { value: startFrame, writable: false, }, - /** - * Stop frame of the frame in the job - * @name stopFrame - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ stopFrame: { value: stopFrame, writable: false, @@ -128,14 +64,6 @@ export class FrameData { value: decodeForward, writable: false, }, - /** - * True if frame was deleted from the task data - * @name deleted - * @type {boolean} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ deleted: { value: deleted, writable: false, @@ -144,18 +72,6 @@ export class FrameData { ); } - /** - * Method returns URL encoded image which can be placed in the img tag - * @method data - * @returns {string} - * @memberof module:API.cvat.classes.FrameData - * @instance - * @async - * @param {function} [onServerRequest = () => {}] - * callback which will be called if data absences local - * @throws {module:API.cvat.exception.ServerError} - * @throws {module:API.cvat.exception.PluginError} - */ async data(onServerRequest = () => {}) { const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest); return result; diff --git a/cvat-core/src/labels.ts b/cvat-core/src/labels.ts index 1e609f76..86b41422 100644 --- a/cvat-core/src/labels.ts +++ b/cvat-core/src/labels.ts @@ -16,11 +16,6 @@ export interface RawAttribute { id?: number; } -/** - * Class representing an attribute - * @memberof module:API.cvat.classes - * @hideconstructor - */ export class Attribute { public id?: number; public defaultValue: string; @@ -58,63 +53,21 @@ export class Attribute { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * @name defaultValue - * @type {string} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ defaultValue: { get: () => data.default_value, }, - /** - * @name inputType - * @type {module:API.cvat.enums.AttributeType} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ inputType: { get: () => data.input_type, }, - /** - * @name mutable - * @type {boolean} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ mutable: { get: () => data.mutable, }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ name: { get: () => data.name, }, - /** - * @name values - * @type {string[]} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ values: { get: () => [...data.values], }, @@ -152,11 +105,6 @@ export interface RawLabel { attributes: RawAttribute[]; } -/** - * Class representing a label - * @memberof module:API.cvat.classes - * @hideconstructor - */ export class Label { public name: string; public readonly id?: number; @@ -209,22 +157,9 @@ export class Label { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.Label - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Label - * @instance - */ name: { get: () => data.name, set: (name) => { @@ -234,12 +169,6 @@ export class Label { data.name = name; }, }, - /** - * @name color - * @type {string} - * @memberof module:API.cvat.classes.Label - * @instance - */ color: { get: () => data.color, set: (color) => { @@ -250,40 +179,12 @@ export class Label { } }, }, - /** - * @name attributes - * @type {module:API.cvat.classes.Attribute[]} - * @memberof module:API.cvat.classes.Label - * @readonly - * @instance - */ attributes: { get: () => [...data.attributes], }, - /** - * @typedef {Object} SkeletonStructure - * @property {module:API.cvat.classes.Label[]} sublabels A list of labels the skeleton includes - * @property {Object[]} svg An SVG representation of the skeleton - * A type of a file - * @global - */ - /** - * @name type - * @type {string | undefined} - * @memberof module:API.cvat.classes.Label - * @readonly - * @instance - */ type: { get: () => data.type, }, - /** - * @name type - * @type {SkeletonStructure | undefined} - * @memberof module:API.cvat.classes.Label - * @readonly - * @instance - */ structure: { get: () => { if (data.type === ShapeType.SKELETON) { @@ -296,25 +197,12 @@ export class Label { return null; }, }, - /** - * @name deleted - * @type {boolean} - * @memberof module:API.cvat.classes.Label - * @instance - */ deleted: { get: () => data.deleted, set: (value) => { data.deleted = value; }, }, - /** - * @name hasParent - * @type {boolean} - * @memberof module:API.cvat.classes.Label - * @readonly - * @instance - */ hasParent: { get: () => data.has_parent, }, diff --git a/cvat-core/src/object-state.ts b/cvat-core/src/object-state.ts index 958c4103..30646e41 100644 --- a/cvat-core/src/object-state.ts +++ b/cvat-core/src/object-state.ts @@ -64,10 +64,6 @@ export interface SerializedData { }; } -/** - * Class representing a state of an object on a specific frame - * @memberof module:API.cvat.classes -*/ export default class ObjectState { private readonly __internal: { save: (objectState: ObjectState) => ObjectState; @@ -105,14 +101,6 @@ export default class ObjectState { public descriptions: string[]; public elements: ObjectState[]; - /** - * @param {Object} serialized - is an dictionary which contains - * initial information about an ObjectState; - *
    Necessary fields: objectType, shapeType, frame, updated, group - *
    Optional fields: keyframes, clientID, serverID, parentID - *
    Optional fields which can be set later: points, zOrder, outside, - * occluded, hidden, attributes, lock, label, color, keyframe, source - */ constructor(serialized: SerializedData) { if (!isEnum.call(ObjectType, serialized.objectType)) { throw new ArgumentError( @@ -200,82 +188,27 @@ export default class ObjectState { get: () => data.updateFlags, }, frame: { - /** - * @name frame - * @type {number} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => data.frame, }, objectType: { - /** - * @name objectType - * @type {module:API.cvat.enums.ObjectType} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => data.objectType, }, shapeType: { - /** - * @name shapeType - * @type {module:API.cvat.enums.ShapeType} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => data.shapeType, }, source: { - /** - * @name source - * @type {module:API.cvat.enums.Source} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => data.source, }, clientID: { - /** - * @name clientID - * @type {number} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => data.clientID, }, serverID: { - /** - * @name serverID - * @type {number} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => data.serverID, }, parentID: { - /** - * @name parentID - * @type {number | null} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => data.parentID, }, label: { - /** - * @name shape - * @type {module:API.cvat.classes.Label} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => data.label, set: (labelInstance) => { data.updateFlags.label = true; @@ -283,12 +216,6 @@ export default class ObjectState { }, }, color: { - /** - * @name color - * @type {string} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => data.color, set: (color) => { data.updateFlags.color = true; @@ -296,12 +223,6 @@ export default class ObjectState { }, }, hidden: { - /** - * @name hidden - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => { if (data.shapeType === ShapeType.SKELETON) { return data.elements.every((element: ObjectState) => element.hidden); @@ -321,13 +242,6 @@ export default class ObjectState { }, }, points: { - /** - * @name points - * @type {number[]} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ get: () => { if (data.shapeType === ShapeType.SKELETON) { return data.elements.map((element) => element.points).flat(); @@ -370,14 +284,6 @@ export default class ObjectState { }, }, rotation: { - /** - * @name rotation - * @description angle measured by degrees - * @type {number} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ get: () => data.rotation, set: (rotation) => { if (typeof rotation === 'number') { @@ -394,23 +300,9 @@ export default class ObjectState { }, }, group: { - /** - * Object with short group info { color, id } - * @name group - * @type {object} - * @memberof module:API.cvat.classes.ObjectState - * @instance - * @readonly - */ get: () => data.group, }, zOrder: { - /** - * @name zOrder - * @type {integer | null} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => data.zOrder, set: (zOrder) => { data.updateFlags.zOrder = true; @@ -418,12 +310,6 @@ export default class ObjectState { }, }, outside: { - /** - * @name outside - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => { if (data.shapeType === ShapeType.SKELETON) { return data.elements.every((el) => el.outside); @@ -442,12 +328,6 @@ export default class ObjectState { }, }, keyframe: { - /** - * @name keyframe - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => { if (data.shapeType === ShapeType.SKELETON) { return data.keyframe || data.elements.some((el) => el.keyframe); @@ -467,14 +347,6 @@ export default class ObjectState { }, }, keyframes: { - /** - * Object of keyframes { first, prev, next, last } - * @name keyframes - * @type {object | null} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ get: () => { if (typeof data.keyframes === 'object') { return { ...data.keyframes }; @@ -484,12 +356,6 @@ export default class ObjectState { }, }, occluded: { - /** - * @name occluded - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => { if (data.shapeType === ShapeType.SKELETON) { return data.elements.every((el) => el.occluded); @@ -508,12 +374,6 @@ export default class ObjectState { }, }, lock: { - /** - * @name lock - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => { if (data.shapeType === ShapeType.SKELETON) { return data.elements.every((el) => el.lock); @@ -532,12 +392,6 @@ export default class ObjectState { }, }, pinned: { - /** - * @name pinned - * @type {boolean | null} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ get: () => { if (typeof data.pinned === 'boolean') { return data.pinned; @@ -551,26 +405,9 @@ export default class ObjectState { }, }, updated: { - /** - * Timestamp of the latest updated of the object - * @name updated - * @type {number} - * @memberof module:API.cvat.classes.ObjectState - * @instance - * @readonly - */ get: () => data.updated, }, attributes: { - /** - * Object is id:value pairs where "id" is an integer - * attribute identifier and "value" is an attribute value - * @name attributes - * @type {Object} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ get: () => data.attributes, set: (attributes) => { if (typeof attributes !== 'object') { @@ -591,14 +428,6 @@ export default class ObjectState { }, }, descriptions: { - /** - * Additional text information displayed on canvas - * @name descripttions - * @type {string[]} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ get: () => [...data.descriptions], set: (descriptions) => { if ( @@ -615,15 +444,6 @@ export default class ObjectState { }, }, elements: { - /** - * Returns a list of object states for compound objects (like skeletons) - * @name elements - * @type {string[]} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @readonly - * @instance - */ get: () => { if (data.elements) { return [...data.elements]; @@ -683,35 +503,11 @@ export default class ObjectState { } } - /** - * Method saves/updates an object state in a collection - * @method save - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @returns {module:API.cvat.classes.ObjectState} updated state of an object - */ async save(): Promise { const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.save); return result; } - /** - * Method deletes an object from a collection - * @method delete - * @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(frame, force = false): Promise { const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.delete, frame, force); return result; diff --git a/cvat-core/src/project.ts b/cvat-core/src/project.ts index 9cc5dd93..636701de 100644 --- a/cvat-core/src/project.ts +++ b/cvat-core/src/project.ts @@ -12,18 +12,7 @@ import { Label } from './labels'; import User from './user'; import { FieldUpdateTrigger } from './common'; -/** - * Class representing a project - * @memberof module:API.cvat.classes - */ export default class Project { - /** - * In a fact you need use the constructor only if you want to create a project - * @param {object} initialData - Object which is used for initialization - *
    It can contain keys: - *
  • name - *
  • labels - */ constructor(initialData) { const data = { id: undefined, @@ -65,23 +54,9 @@ export default class Project { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Project - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ name: { get: () => data.name, set: (value) => { @@ -92,25 +67,9 @@ export default class Project { updateTrigger.update('name'); }, }, - - /** - * @name status - * @type {module:API.cvat.enums.TaskStatus} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ status: { get: () => data.status, }, - /** - * Instance of a user who was assigned for the project - * @name assignee - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ assignee: { get: () => data.assignee, set: (assignee) => { @@ -121,24 +80,9 @@ export default class Project { updateTrigger.update('assignee'); }, }, - /** - * Instance of a user who has created the project - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ owner: { get: () => data.owner, }, - /** - * @name bugTracker - * @type {string} - * @memberof module:API.cvat.classes.Project - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ bugTracker: { get: () => data.bug_tracker, set: (tracker) => { @@ -146,45 +90,15 @@ export default class Project { updateTrigger.update('bugTracker'); }, }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ createdDate: { get: () => data.created_date, }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ updatedDate: { get: () => data.updated_date, }, - /** - * Dimesion of the tasks in the project, if no task dimension is null - * @name dimension - * @type {string} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ dimension: { get: () => data.dimension, }, - /** - * After project has been created value can be appended only. - * @name labels - * @type {module:API.cvat.classes.Label[]} - * @memberof module:API.cvat.classes.Project - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ labels: { get: () => [...data.labels], set: (labels) => { @@ -208,27 +122,9 @@ export default class Project { updateTrigger.update('labels'); }, }, - /** - * Subsets array for related tasks - * @name subsets - * @type {string[]} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ subsets: { get: () => [...data.task_subsets], }, - /** - * Training project associated with this annotation project - * This is a simple object which contains - * keys like host, username, password, enabled, project_class - * @name trainingProject - * @type {object} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ trainingProject: { get: () => { if (typeof data.training_project === 'object') { @@ -245,14 +141,6 @@ export default class Project { updateTrigger.update('trainingProject'); }, }, - /** - * Source storage for import resources. - * @name sourceStorage - * @type {module:API.cvat.classes.Storage} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ sourceStorage: { get: () => ( new Storage({ @@ -261,14 +149,6 @@ export default class Project { }) ), }, - /** - * Target storage for export resources. - * @name targetStorage - * @type {module:API.cvat.classes.Storage} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ targetStorage: { get: () => ( new Storage({ @@ -295,64 +175,21 @@ export default class Project { }; } - /** - * Get the first frame of the first task of a project for preview - * @method preview - * @memberof Project - * @returns {string} - jpeg encoded image - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ async preview() { const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.preview); return result; } - /** - * Method updates data of a created project or creates new project from scratch - * @method save - * @returns {module:API.cvat.classes.Project} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async save() { const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.save); return result; } - /** - * Method deletes a project from a server - * @method delete - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async delete() { const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.delete); return result; } - /** - * Method makes a backup of a project - * @method backup - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @returns {string} URL to get result archive - */ async backup(targetStorage: Storage, useDefaultSettings: boolean, fileName?: string) { const result = await PluginRegistry.apiWrapper.call( this, @@ -364,17 +201,6 @@ export default class Project { return result; } - /** - * Method restores a project from a backup - * @method restore - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @returns {number} ID of the imported project - */ static async restore(storage: Storage, file: File | string) { const result = await PluginRegistry.apiWrapper.call(this, Project.restore, storage, file); return result; diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts index 57a4a73d..7b86f4b2 100644 --- a/cvat-core/src/session.ts +++ b/cvat-core/src/session.ts @@ -311,481 +311,8 @@ function buildDuplicatedAPI(prototype) { }); } -/** - * Base abstract class for Task and Job. It contains common members. - * @hideconstructor - * @virtual - */ -export class Session { - /** - * An interaction with annotations - * @namespace annotations - * @memberof Session - */ - /** - * Upload annotations from a dump file - * You need upload annotations from a server again after successful executing - * @method upload - * @memberof Session.annotations - * @param {File} annotations - a file with annotations - * @param {module:API.cvat.classes.Loader} loader - a loader - * which will be used to upload - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * Save all changes in annotations on a server - * Objects which hadn't been saved on a server before, - * get a serverID after saving. But received object states aren't updated. - * So, after successful saving it's recommended to update them manually - * (call the annotations.get() again) - * @method save - * @memberof Session.annotations - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @instance - * @async - * @param {function} [onUpdate] saving can be long. - * This callback can be used to notify a user about current progress - * Its argument is a text string - */ - /** - * Remove all annotations and optionally reinitialize it - * @method clear - * @memberof Session.annotations - * @param {boolean} [reload = false] reset all changes and - * reinitialize annotations by data from a server - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @instance - * @async - */ - /** - * Collect short statistics about a task or a job. - * @method statistics - * @memberof Session.annotations - * @returns {module:API.cvat.classes.Statistics} statistics object - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Create new objects from one-frame states - * After successful adding you need to update object states on a frame - * @method put - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} data - * @returns {number[]} identificators of added objects - * array of objects on the specific frame - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.DataError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Get annotations for a specific frame - *
    Filter supports following operators: - * ==, !=, >, >=, <, <=, ~= and (), |, & for grouping. - *
    Filter supports properties: - * width, height, label, serverID, clientID, type, shape, occluded - *
    All prop values are case-sensitive. CVAT uses json queries for search. - *
    Examples: - *
      - *
    • label=="car" | label==["road sign"]
    • - *
    • width >= height
    • - *
    • attr["Attribute 1"] == attr["Attribute 2"]
    • - *
    • type=="track" & shape="rectangle"
    • - *
    • clientID == 50
    • - *
    • (label=="car" & attr["parked"]==true) - * | (label=="pedestrian" & width > 150)
    • - *
    • (( label==["car \"mazda\""]) & - * (attr["sunglass ( help ) es"]==true | - * (width > 150 | height > 150 & (clientID == serverID)))))
    • - *
    - * If you have double quotes in your query string, - * please escape them using back slash: \" - * @method get - * @param {number} frame get objects from the frame - * @param {boolean} allTracks show all tracks - * even if they are outside and not keyframe - * @param {any[]} [filters = []] - * get only objects that satisfied to specific filters - * @returns {module:API.cvat.classes.ObjectState[]} - * @memberof Session.annotations - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Find a frame in the range [from, to] - * that contains at least one object satisfied to a filter - * @method search - * @memberof Session.annotations - * @param {ObjectFilter} [filter = []] filter - * @param {number} from lower bound of a search - * @param {number} to upper bound of a search - * @returns {number|null} a frame that contains objects according to the filter - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Find the nearest empty frame without any annotations - * @method searchEmpty - * @memberof Session.annotations - * @param {number} from lower bound of a search - * @param {number} to upper bound of a search - * @returns {number|null} a empty frame according boundaries - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Select shape under a cursor by using minimal distance - * between a cursor and a shape edge or a shape point - * For closed shapes a cursor is placed inside a shape - * @method select - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} objectStates - * objects which can be selected - * @param {float} x horizontal coordinate - * @param {float} y vertical coordinate - * @returns {Object} - * a pair of {state: ObjectState, distance: number} for selected object. - * Pair values can be null if there aren't any sutisfied objects - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Method unites several shapes and tracks into the one - * All shapes must be the same (rectangle, polygon, etc) - * All labels must be the same - * After successful merge you need to update object states on a frame - * @method merge - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} objectStates - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Method splits a track into two parts - * (start frame: previous frame), (frame, last frame) - * After successful split you need to update object states on a frame - * @method split - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState} objectState - * @param {number} frame - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Method creates a new group and put all passed objects into it - * After successful split you need to update object states on a frame - * @method group - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} objectStates - * @param {boolean} reset pass "true" to reset group value (set it to 0) - * @returns {number} an ID of created group - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Method indicates if there are any changes in - * annotations which haven't been saved on a server - *
    This function cannot be wrapped with a plugin - * @method hasUnsavedChanges - * @memberof Session.annotations - * @returns {boolean} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - */ - /** - * - * Import raw data in a collection - * @method import - * @memberof Session.annotations - * @param {Object} data - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * - * Export a collection as a row data - * @method export - * @memberof Session.annotations - * @returns {Object} data - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Export as a dataset. - * Method builds a dataset in the specified format. - * @method exportDataset - * @memberof Session.annotations - * @param {module:String} format - a format - * @returns {string} An URL to the dataset file - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Namespace is used for an interaction with frames - * @namespace frames - * @memberof Session - */ - /** - * Get frame by its number - * @method get - * @memberof Session.frames - * @param {number} frame number of frame which you want to get - * @returns {module:API.cvat.classes.FrameData} - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.DataError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * @typedef {Object} FrameSearchFilters - * @property {boolean} notDeleted if true will search for non-deleted frames - * @property {number} offset defines frame step during search - /** - * Find frame that match the condition - * @method search - * @memberof Session.frames - * @param {FrameSearchFilters} filters filters to search frame for - * @param {number} from lower bound of a search - * @param {number} to upper bound of a search - * @returns {number|null} a non-deleted frame according boundaries - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Delete frame from the job - * @method delete - * @memberof Session.frames - * @param {number} frame number of frame which you want to delete - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Restore frame from the job - * @method delete - * @memberof Session.frames - * @param {number} frame number of frame which you want to restore - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Save any changes in frames if some of them were deleted/restored - * @method save - * @memberof Session.frames - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Get the first frame of a task for preview - * @method preview - * @memberof Session.frames - * @returns {string} - jpeg encoded image - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * Returns the ranges of cached frames - * @method ranges - * @memberof Session.frames - * @returns {Array.} - * @instance - * @async - */ - /** - * Namespace is used for an interaction with logs - * @namespace logger - * @memberof Session - */ - /** - * Create a log and add it to a log collection
    - * Durable logs will be added after "close" method is called for them
    - * The fields "task_id" and "job_id" automatically added when add logs - * through a task or a job
    - * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
    - * Payload of ignored logs are shallowly combined to previous logs of the same type - * @method log - * @memberof Session.logger - * @param {module:API.cvat.enums.LogType | string} type - log type - * @param {Object} [payload = {}] - any other data that will be appended to the log - * @param {boolean} [wait = false] - specifies if log is durable - * @returns {module:API.cvat.classes.Log} - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * Namespace is used for an interaction with actions - * @namespace actions - * @memberof Session - */ - /** - * @typedef {Object} HistoryActions - * @property {string[]} [undo] - array of possible actions to undo - * @property {string[]} [redo] - array of possible actions to redo - * @global - */ - /** - * Make undo - * @method undo - * @memberof Session.actions - * @param {number} [count=1] number of actions to undo - * @returns {number[]} Array of affected objects - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Make redo - * @method redo - * @memberof Session.actions - * @param {number} [count=1] number of actions to redo - * @returns {number[]} Array of affected objects - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Freeze history (do not save new actions) - * @method freeze - * @memberof Session.actions - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Remove all actions from history - * @method clear - * @memberof Session.actions - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Get actions - * @method get - * @memberof Session.actions - * @returns {HistoryActions} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @returns {Array.>} - * array of pairs [action name, frame number] - * @instance - * @async - */ - /** - * Namespace is used for an interaction with events - * @namespace events - * @memberof Session - */ - /** - * Subscribe on an event - * @method subscribe - * @memberof Session.events - * @param {module:API.cvat.enums.EventType} type - event type - * @param {functions} callback - function which will be called on event - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Unsubscribe from an event. If callback is not provided, - * all callbacks will be removed from subscribers for the event - * @method unsubscribe - * @memberof Session.events - * @param {module:API.cvat.enums.EventType} type - event type - * @param {functions} [callback = null] - function which is called on event - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * @typedef {Object} PredictorStatus - * @property {string} message - message for a user to be displayed somewhere - * @property {number} projectScore - model accuracy - * @global - */ - /** - * Namespace is used for an interaction with events - * @namespace predictor - * @memberof Session - */ - /** - * Subscribe to updates of a ML model binded to the project - * @method status - * @memberof Session.predictor - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @returns {PredictorStatus} - * @instance - * @async - */ - /** - * Get predictions from a ML model binded to the project - * @method predict - * @memberof Session.predictor - * @param {number} frame - number of frame to inference - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.DataError} - * @returns {object[] | null} annotations - * @instance - * @async - */ -} +export class Session {} -/** - * Class representing a job. - * @memberof module:API.cvat.classes - * @hideconstructor - * @extends Session - */ export class Job extends Session { constructor(initialData) { super(); @@ -838,24 +365,9 @@ export class Job extends Session { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * Instance of a user who is responsible for the job annotations - * @name assignee - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Job - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ assignee: { get: () => data.assignee, set: (assignee) => { @@ -866,13 +378,6 @@ export class Job extends Session { data.assignee = assignee; }, }, - /** - * @name stage - * @type {module:API.cvat.enums.JobStage} - * @memberof module:API.cvat.classes.Job - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ stage: { get: () => data.stage, set: (stage) => { @@ -895,13 +400,6 @@ export class Job extends Session { data.stage = stage; }, }, - /** - * @name state - * @type {module:API.cvat.enums.JobState} - * @memberof module:API.cvat.classes.Job - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ state: { get: () => data.state, set: (state) => { @@ -924,73 +422,24 @@ export class Job extends Session { data.state = state; }, }, - /** - * @name startFrame - * @type {number} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ startFrame: { get: () => data.start_frame, }, - /** - * @name stopFrame - * @type {number} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ stopFrame: { get: () => data.stop_frame, }, - /** - * @name projectId - * @type {number|null} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ projectId: { get: () => data.project_id, }, - /** - * @name taskId - * @type {number} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ taskId: { get: () => data.task_id, }, - /** - * @name labels - * @type {module:API.cvat.classes.Label[]} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ labels: { get: () => data.labels.filter((_label) => !_label.deleted), }, - /** - * @name dimension - * @type {module:API.cvat.enums.DimensionType} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ dimension: { get: () => data.dimension, }, - /** - * @name dataChunkSize - * @type {number} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ dataChunkSize: { get: () => data.data_chunk_size, set: (chunkSize) => { @@ -1003,33 +452,12 @@ export class Job extends Session { data.data_chunk_size = chunkSize; }, }, - /** - * @name dataChunkSize - * @type {string} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ dataChunkType: { get: () => data.data_compressed_chunk_type, }, - /** - * @name mode - * @type {string} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ mode: { get: () => data.mode, }, - /** - * @name bugTracker - * @type {string|null} - * @memberof module:API.cvat.classes.Job - * @instance - * @readonly - */ bugTracker: { get: () => data.bug_tracker, }, @@ -1090,89 +518,28 @@ export class Job extends Session { }; } - /** - * Method updates job data like state, stage or assignee - * @method save - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async save() { const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.save); return result; } - /** - * Method returns a list of issues for a job - * @method issues - * @memberof module:API.cvat.classes.Job - * @returns {module:API.cvat.classes.Issue[]} - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async issues() { const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.issues); return result; } - /** - * Method adds a new issue to a job - * @method openIssue - * @memberof module:API.cvat.classes.Job - * @returns {module:API.cvat.classes.Issue} - * @param {module:API.cvat.classes.Issue} issue - * @param {string} message - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async openIssue(issue, message) { const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.openIssue, issue, message); return result; } - /** - * Method removes all job related data from the client (annotations, history, etc.) - * @method close - * @returns {module:API.cvat.classes.Job} - * @memberof module:API.cvat.classes.Job - * @readonly - * @async - * @instance - * @throws {module:API.cvat.exceptions.PluginError} - */ async close() { const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.close); return result; } } -/** - * Class representing a task - * @memberof module:API.cvat.classes - * @extends Session - */ export class Task extends Session { - /** - * In a fact you need use the constructor only if you want to create a task - * @param {object} initialData - Object which is used for initialization - *
    It can contain keys: - *
  • name - *
  • assignee - *
  • bug_tracker - *
  • labels - *
  • segment_size - *
  • overlap - */ constructor(initialData) { super(); const data = { @@ -1265,23 +632,9 @@ export class Task extends Session { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ name: { get: () => data.name, set: (value) => { @@ -1292,12 +645,6 @@ export class Task extends Session { data.name = value; }, }, - /** - * @name projectId - * @type {number|null} - * @memberof module:API.cvat.classes.Task - * @instance - */ projectId: { get: () => data.project_id, set: (projectId) => { @@ -1309,55 +656,18 @@ export class Task extends Session { data.project_id = projectId; }, }, - /** - * @name status - * @type {module:API.cvat.enums.TaskStatus} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ status: { get: () => data.status, }, - /** - * @name size - * @type {number} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ size: { get: () => data.size, }, - /** - * @name mode - * @type {TaskMode} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ mode: { get: () => data.mode, }, - /** - * Instance of a user who has created the task - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ owner: { get: () => data.owner, }, - /** - * Instance of a user who is responsible for the task - * @name assignee - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ assignee: { get: () => data.assignee, set: (assignee) => { @@ -1368,33 +678,12 @@ export class Task extends Session { data.assignee = assignee; }, }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ createdDate: { get: () => data.created_date, }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ updatedDate: { get: () => data.updated_date, }, - /** - * @name bugTracker - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ bugTracker: { get: () => data.bug_tracker, set: (tracker) => { @@ -1408,13 +697,6 @@ export class Task extends Session { data.bug_tracker = tracker; }, }, - /** - * @name subset - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exception.ArgumentError} - */ subset: { get: () => data.subset, set: (subset) => { @@ -1428,13 +710,6 @@ export class Task extends Session { data.subset = subset; }, }, - /** - * @name overlap - * @type {number} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ overlap: { get: () => data.overlap, set: (overlap) => { @@ -1444,13 +719,6 @@ export class Task extends Session { data.overlap = overlap; }, }, - /** - * @name segmentSize - * @type {number} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ segmentSize: { get: () => data.segment_size, set: (segment) => { @@ -1460,13 +728,6 @@ export class Task extends Session { data.segment_size = segment; }, }, - /** - * @name imageQuality - * @type {number} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ imageQuality: { get: () => data.image_quality, set: (quality) => { @@ -1476,13 +737,6 @@ export class Task extends Session { data.image_quality = quality; }, }, - /** - * @name useZipChunks - * @type {boolean} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ useZipChunks: { get: () => data.use_zip_chunks, set: (useZipChunks) => { @@ -1492,13 +746,6 @@ export class Task extends Session { data.use_zip_chunks = useZipChunks; }, }, - /** - * @name useCache - * @type {boolean} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ useCache: { get: () => data.use_cache, set: (useCache) => { @@ -1508,13 +755,6 @@ export class Task extends Session { data.use_cache = useCache; }, }, - /** - * @name copyData - * @type {boolean} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ copyData: { get: () => data.copy_data, set: (copyData) => { @@ -1524,13 +764,6 @@ export class Task extends Session { data.copy_data = copyData; }, }, - /** - * @name labels - * @type {module:API.cvat.classes.Label[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ labels: { get: () => data.labels.filter((_label) => !_label.deleted), set: (labels) => { @@ -1556,24 +789,9 @@ export class Task extends Session { data.labels = [...deletedLabels, ...labels]; }, }, - /** - * @name jobs - * @type {module:API.cvat.classes.Job[]} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ jobs: { get: () => [...data.jobs], }, - /** - * List of files from shared resource or list of cloud storage files - * @name serverFiles - * @type {string[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ serverFiles: { get: () => [...data.files.server_files], set: (serverFiles) => { @@ -1594,14 +812,6 @@ export class Task extends Session { Array.prototype.push.apply(data.files.server_files, serverFiles); }, }, - /** - * List of files from client host - * @name clientFiles - * @type {File[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ clientFiles: { get: () => [...data.files.client_files], set: (clientFiles) => { @@ -1622,14 +832,6 @@ export class Task extends Session { Array.prototype.push.apply(data.files.client_files, clientFiles); }, }, - /** - * List of files from remote host - * @name remoteFiles - * @type {File[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ remoteFiles: { get: () => [...data.files.remote_files], set: (remoteFiles) => { @@ -1650,14 +852,6 @@ export class Task extends Session { Array.prototype.push.apply(data.files.remote_files, remoteFiles); }, }, - /** - * The first frame of a video to annotation - * @name startFrame - * @type {number} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ startFrame: { get: () => data.start_frame, set: (frame) => { @@ -1667,14 +861,6 @@ export class Task extends Session { data.start_frame = frame; }, }, - /** - * The last frame of a video to annotation - * @name stopFrame - * @type {number} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ stopFrame: { get: () => data.stop_frame, set: (frame) => { @@ -1684,14 +870,6 @@ export class Task extends Session { data.stop_frame = frame; }, }, - /** - * Filter to ignore some frames during task creation - * @name frameFilter - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ frameFilter: { get: () => data.frame_filter, set: (filter) => { @@ -1719,43 +897,15 @@ export class Task extends Session { dataChunkType: { get: () => data.data_compressed_chunk_type, }, - /** - * @name dimension - * @type {module:API.cvat.enums.DimensionType} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ dimension: { get: () => data.dimension, }, - /** - * @name cloudStorageId - * @type {integer|null} - * @memberof module:API.cvat.classes.Task - * @instance - */ cloudStorageId: { get: () => data.cloud_storage_id, }, sortingMethod: { - /** - * @name sortingMethod - * @type {module:API.cvat.enums.SortingMethod} - * @memberof module:API.cvat.classes.Task - * @instance - * @readonly - */ get: () => data.sorting_method, }, - /** - * Source storage for import resources. - * @name sourceStorage - * @type {module:API.cvat.classes.Storage} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ sourceStorage: { get: () => ( new Storage({ @@ -1764,14 +914,6 @@ export class Task extends Session { }) ), }, - /** - * Target storage for export resources. - * @name targetStorage - * @type {module:API.cvat.classes.Storage} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ targetStorage: { get: () => ( new Storage({ @@ -1840,65 +982,21 @@ export class Task extends Session { }; } - /** - * Method removes all task related data from the client (annotations, history, etc.) - * @method close - * @returns {module:API.cvat.classes.Task} - * @memberof module:API.cvat.classes.Task - * @readonly - * @async - * @instance - * @throws {module:API.cvat.exceptions.PluginError} - */ async close() { const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.close); return result; } - /** - * Method updates data of a created task or creates new task from scratch - * @method save - * @returns {module:API.cvat.classes.Task} - * @memberof module:API.cvat.classes.Task - * @param {function} [onUpdate] - the function which is used only if task hasn't - * been created yet. It called in order to notify about creation status. - * It receives the string parameter which is a status message - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async save(onUpdate = () => {}) { const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.save, onUpdate); return result; } - /** - * Method deletes a task from a server - * @method delete - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async delete() { const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.delete); return result; } - /** - * Method makes a backup of a task - * @method backup - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ async backup(targetStorage: Storage, useDefaultSettings: boolean, fileName?: string) { const result = await PluginRegistry.apiWrapper.call( this, @@ -1910,16 +1008,6 @@ export class Task extends Session { return result; } - /** - * Method restores a task from a backup - * @method restore - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ static async restore(storage: Storage, file: File | string) { const result = await PluginRegistry.apiWrapper.call(this, Task.restore, storage, file); return result; diff --git a/cvat-core/src/storage.ts b/cvat-core/src/storage.ts index 9c0e8d32..851e9510 100644 --- a/cvat-core/src/storage.ts +++ b/cvat-core/src/storage.ts @@ -14,11 +14,6 @@ interface StorageJsonData { cloud_storage_id?: number; } -/** - * Class representing a storage for import and export resources - * @memberof module:API.cvat.classes - * @hideconstructor - */ export class Storage { public location: StorageLocation; public cloudStorageId: number; @@ -32,23 +27,9 @@ export class Storage { Object.defineProperties( this, Object.freeze({ - /** - * @name location - * @type {module:API.cvat.enums.StorageLocation} - * @memberof module:API.cvat.classes.Storage - * @instance - * @readonly - */ location: { get: () => data.location, }, - /** - * @name cloudStorageId - * @type {number} - * @memberof module:API.cvat.classes.Storage - * @instance - * @readonly - */ cloudStorageId: { get: () => data.cloudStorageId, }, diff --git a/cvat-core/src/user.ts b/cvat-core/src/user.ts index 540f72a7..4dea7e18 100644 --- a/cvat-core/src/user.ts +++ b/cvat-core/src/user.ts @@ -58,123 +58,39 @@ export default class User { this, Object.freeze({ id: { - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.id, }, username: { - /** - * @name username - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.username, }, email: { - /** - * @name email - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.email, }, firstName: { - /** - * @name firstName - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.first_name, }, lastName: { - /** - * @name lastName - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.last_name, }, groups: { - /** - * @name groups - * @type {string[]} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => JSON.parse(JSON.stringify(data.groups)), }, lastLogin: { - /** - * @name lastLogin - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.last_login, }, dateJoined: { - /** - * @name dateJoined - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.date_joined, }, isStaff: { - /** - * @name isStaff - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.is_staff, }, isSuperuser: { - /** - * @name isSuperuser - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.is_superuser, }, isActive: { - /** - * @name isActive - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => data.is_active, }, isVerified: { - /** - * @name isVerified - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ get: () => !data.email_verification_required, }, }), diff --git a/cvat-core/src/webhook.ts b/cvat-core/src/webhook.ts index 09424cff..559d9105 100644 --- a/cvat-core/src/webhook.ts +++ b/cvat-core/src/webhook.ts @@ -7,6 +7,7 @@ import User from './user'; import serverProxy from './server-proxy'; import { WebhookSourceType, WebhookContentType } from './enums'; import { isEnum } from './common'; +import { ArgumentError } from './exceptions'; interface RawWebhookData { id?: number; @@ -93,7 +94,7 @@ export default class Webhook { get: () => data.target_url, set: (value: string) => { if (typeof value !== 'string') { - throw ArgumentError( + throw new ArgumentError( `targetURL property must be a string, tried to set ${typeof value}`, ); } @@ -104,13 +105,13 @@ export default class Webhook { get: () => data.events, set: (events: string[]) => { if (!Array.isArray(events)) { - throw ArgumentError( + throw new ArgumentError( `Events must be an array, tried to set ${typeof events}`, ); } events.forEach((event: string) => { if (typeof event !== 'string') { - throw ArgumentError( + throw new ArgumentError( `Event must be a string, tried to set ${typeof event}`, ); } @@ -140,7 +141,7 @@ export default class Webhook { get: () => data.description, set: (value: string) => { if (typeof value !== 'string') { - throw ArgumentError( + throw new ArgumentError( `Description property must be a string, tried to set ${typeof value}`, ); } @@ -151,7 +152,7 @@ export default class Webhook { get: () => data.secret, set: (value: string) => { if (typeof value !== 'string') { - throw ArgumentError( + throw new ArgumentError( `Secret property must be a string, tried to set ${typeof value}`, ); } @@ -162,7 +163,7 @@ export default class Webhook { get: () => data.is_active, set: (value: boolean) => { if (typeof value !== 'boolean') { - throw ArgumentError( + throw new ArgumentError( `isActive property must be a boolean, tried to set ${typeof value}`, ); } @@ -173,7 +174,7 @@ export default class Webhook { get: () => data.enable_ssl, set: (value: boolean) => { if (typeof value !== 'boolean') { - throw ArgumentError( + throw new ArgumentError( `enableSSL property must be a boolean, tried to set ${typeof value}`, ); } diff --git a/yarn.lock b/yarn.lock index f6ecdb92..70c3b556 100644 --- a/yarn.lock +++ b/yarn.lock @@ -319,7 +319,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.13", "@babel/parser@^7.7.0", "@babel/parser@^7.9.4": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.13", "@babel/parser@^7.7.0": version "7.18.13" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4" integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg== @@ -1813,24 +1813,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/linkify-it@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" - integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== - "@types/lodash@^4.14.172": version "4.14.184" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" integrity sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q== -"@types/markdown-it@^12.2.3": - version "12.2.3" - resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" - integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== - dependencies: - "@types/linkify-it" "*" - "@types/mdurl" "*" - "@types/mdast@^3.0.0": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" @@ -1838,11 +1825,6 @@ dependencies: "@types/unist" "*" -"@types/mdurl@*": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" - integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== - "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -2633,11 +2615,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - aria-query@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" @@ -3155,11 +3132,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bluebird@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - body-parser@1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" @@ -3408,13 +3380,6 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== -catharsis@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121" - integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A== - dependencies: - lodash "^4.17.15" - ccount@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" @@ -4099,10 +4064,11 @@ custom-error-instance@2.1.1: integrity sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg== "cvat-canvas3d@link:./cvat-canvas3d": - version "0.0.4" + version "0.0.6" dependencies: "@types/three" "^0.125.3" camera-controls "^1.25.3" + cvat-core "link:./cvat-core" three "^0.126.1" "cvat-canvas@link:./cvat-canvas": @@ -4119,7 +4085,7 @@ custom-error-instance@2.1.1: svg.select.js "3.0.1" "cvat-core@link:./cvat-core": - version "7.2.2" + version "7.4.0" dependencies: axios "^0.27.2" browser-or-node "^2.0.0" @@ -4624,11 +4590,6 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - envinfo@^7.7.3: version "7.8.1" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" @@ -5695,7 +5656,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.9, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -7429,39 +7390,11 @@ js-yaml@^3.13.1, js-yaml@^3.6.1: argparse "^1.0.7" esprima "^4.0.0" -js2xmlparser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a" - integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA== - dependencies: - xmlcreate "^2.0.4" - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -jsdoc@^3.6.6: - version "3.6.11" - resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce" - integrity sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg== - dependencies: - "@babel/parser" "^7.9.4" - "@types/markdown-it" "^12.2.3" - bluebird "^3.7.2" - catharsis "^0.9.0" - escape-string-regexp "^2.0.0" - js2xmlparser "^4.0.2" - klaw "^3.0.0" - markdown-it "^12.3.2" - markdown-it-anchor "^8.4.1" - marked "^4.0.10" - mkdirp "^1.0.4" - requizzle "^0.2.3" - strip-json-comments "^3.1.0" - taffydb "2.6.2" - underscore "~1.13.2" - jsdom@^16.4.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" @@ -7678,13 +7611,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" - integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== - dependencies: - graceful-fs "^4.1.9" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -7764,13 +7690,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkify-it@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" - integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== - dependencies: - uc.micro "^1.0.1" - lint-staged@^13.0.3: version "13.0.3" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.3.tgz#d7cdf03a3830b327a2b63c6aec953d71d9dc48c6" @@ -7935,7 +7854,7 @@ lodash.uniqby@4.5.0: lodash._baseiteratee "~4.7.0" lodash._baseuniq "~4.6.0" -lodash@4.x, lodash@^4.0.1, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: +lodash@4.x, lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8060,22 +7979,6 @@ markdown-extensions@^1.1.0: resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3" integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q== -markdown-it-anchor@^8.4.1: - version "8.6.4" - resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.4.tgz#affb8aa0910a504c114e9fcad53ac3a5b907b0e6" - integrity sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img== - -markdown-it@^12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" - integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== - dependencies: - argparse "^2.0.1" - entities "~2.1.0" - linkify-it "^3.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - markdown-table@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" @@ -8083,11 +7986,6 @@ markdown-table@^2.0.0: dependencies: repeat-string "^1.0.0" -marked@^4.0.10: - version "4.0.19" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.19.tgz#d36198d1ac1255525153c351c68c75bc1d7aee46" - integrity sha512-rgQF/OxOiLcvgUAj1Q1tAf4Bgxn5h5JZTp04Fx4XUkVhs7B+7YA9JEWJhJpoO8eJt8MkZMwqLCNeNqj1bCREZQ== - material-colors@^1.2.1: version "1.2.6" resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" @@ -8209,11 +8107,6 @@ mdn-data@2.0.4: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -8474,7 +8367,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@1.x, mkdirp@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -11384,13 +11277,6 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -requizzle@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.3.tgz#4675c90aacafb2c036bd39ba2daa4a1cb777fded" - integrity sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ== - dependencies: - lodash "^4.17.14" - resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" @@ -12486,11 +12372,6 @@ table@^6.0.9, table@^6.6.0: string-width "^4.2.3" strip-ansi "^6.0.1" -taffydb@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" - integrity sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA== - tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -12876,11 +12757,6 @@ typescript@^3.7.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -12896,11 +12772,6 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -underscore@~1.13.2: - version "1.13.4" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" - integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -13633,11 +13504,6 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmlcreate@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be" - integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg== - y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"