From 6025151c6f79bbc090625d76158e016d287406a0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Mon, 15 Jul 2019 14:04:08 +0300 Subject: [PATCH] CVAT.js other implemented API methods and bug fixes (#569) * Split implementation * Group implementation * Reset cache for created object after saving * A litle fix * API methods definitions * Typos * API methods annotations.Statistics() and annotationsclear() * Object selection * Little fix * Comments and docs a little changed --- cvatjs/src/annotations-collection.js | 277 ++++++++++++++++++++++++--- cvatjs/src/annotations-objects.js | 167 +++++++++++++++- cvatjs/src/annotations-saver.js | 4 + cvatjs/src/annotations.js | 74 ++++++- cvatjs/src/common.js | 10 +- cvatjs/src/session.js | 138 ++++++++++--- cvatjs/src/statistics.js | 60 +++--- 7 files changed, 625 insertions(+), 105 deletions(-) diff --git a/cvatjs/src/annotations-collection.js b/cvatjs/src/annotations-collection.js index 9715d8fbf5ce..e39757b602d1 100644 --- a/cvatjs/src/annotations-collection.js +++ b/cvatjs/src/annotations-collection.js @@ -23,6 +23,7 @@ objectStateFactory, } = require('./annotations-objects'); const { checkObjectType } = require('./common'); + const Statistics = require('./statistics'); const colors = [ '#0066FF', '#AF593E', '#01A368', '#FF861F', '#ED0A3F', '#FF3F34', '#76D7EA', @@ -115,10 +116,14 @@ this.objects = {}; // key is a client id this.count = 0; this.flush = false; - this.collectionZ = {}; // key is a frame + this.collectionZ = {}; // key is a frame, {max, min} are values + this.groups = { + max: 0, + }; // it is an object to we can pass it as an argument by a reference this.injection = { labels: this.labels, collectionZ: this.collectionZ, + groups: this.groups, }; } @@ -173,16 +178,6 @@ return data; } - empty() { - this.shapes = {}; - this.tags = {}; - this.tracks = []; - this.objects = {}; // by id - this.count = 0; - - this.flush = true; - } - get(frame) { const { tracks } = this; const shapes = this.shapes[frame] || []; @@ -206,7 +201,7 @@ } merge(objectStates) { - checkObjectType('merged shapes', objectStates, null, Array); + checkObjectType('shapes for merge', objectStates, null, Array); if (!objectStates.length) return; const objectsForMerge = objectStates.map((state) => { checkObjectType('object state', state, null, window.cvat.classes.ObjectState); @@ -364,30 +359,264 @@ }; const trackModel = trackFactory(track, clientID, this.injection); - if (trackModel) { - this.tracks.push(trackModel); - this.objects[clientID] = trackModel; - } + this.tracks.push(trackModel); + this.objects[clientID] = trackModel; // Remove other shapes for (const object of objectsForMerge) { object.removed = true; + object.resetCache(); } } - split(objectState) { - checkObjectType('object state', objectState, window.cvat.classes.ObjectState, null); + split(objectState, frame) { + checkObjectType('object state', objectState, null, window.cvat.classes.ObjectState); + checkObjectType('frame', frame, 'integer', null); + + const object = this.objects[objectState.clientID]; + if (typeof (object) === 'undefined') { + throw new window.cvat.exceptions.ArgumentError( + 'The object has not been saved yet. Call annotations.put(state) before', + ); + } + + if (objectState.objectType !== window.cvat.enums.ObjectType.TRACK) { + return; + } + + const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b); + if (frame <= +keyframes[0]) { + return; + } - // TODO: split + const labelAttributes = object.label.attributes.reduce((accumulator, attribute) => { + accumulator[attribute.id] = attribute; + return accumulator; + }, {}); + + const exported = object.toJSON(); + const position = { + type: objectState.shapeType, + points: [...objectState.points], + occluded: objectState.occluded, + outside: objectState.outside, + zOrder: 0, + attributes: Object.keys(objectState.attributes) + .reduce((accumulator, attrID) => { + if (!labelAttributes[attrID].mutable) { + accumulator.push({ + spec_id: +attrID, + value: objectState.attributes[attrID], + }); + } + + return accumulator; + }, []), + frame, + }; + + const prev = { + frame: exported.frame, + group: 0, + label_id: exported.label_id, + attributes: exported.attributes, + shapes: [], + }; + + const next = JSON.parse(JSON.stringify(prev)); + next.frame = frame; + + next.shapes.push(JSON.parse(JSON.stringify(position))); + exported.shapes.map((shape) => { + delete shape.id; + 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))); + } + + return shape; + }); + prev.shapes.push(position); + prev.shapes[prev.shapes.length - 1].outside = true; + + let clientID = ++this.count; + const prevTrack = trackFactory(prev, clientID, this.injection); + this.tracks.push(prevTrack); + this.objects[clientID] = prevTrack; + + clientID = ++this.count; + const nextTrack = trackFactory(next, clientID, this.injection); + this.tracks.push(nextTrack); + this.objects[clientID] = nextTrack; + + // Remove source object + object.removed = true; + object.resetCache(); } - group(array) { - checkObjectType('merged shapes', array, Array, null); - for (const shape of array) { - checkObjectType('object state', shape, window.cvat.classes.ObjectState, null); + group(objectStates, reset) { + checkObjectType('shapes for group', objectStates, null, Array); + + const objectsForGroup = objectStates.map((state) => { + checkObjectType('object state', state, null, window.cvat.classes.ObjectState); + const object = this.objects[state.clientID]; + if (typeof (object) === 'undefined') { + throw new window.cvat.exceptions.ArgumentError( + 'The object has not been saved yet. Call annotations.put(state) before', + ); + } + return object; + }); + + const groupIdx = reset ? 0 : ++this.groups.max; + for (const object of objectsForGroup) { + object.group = groupIdx; + object.resetCache(); } + } - // TODO: + clear() { + this.shapes = {}; + this.tags = {}; + this.tracks = []; + this.objects = {}; // by id + this.count = 0; + + this.flush = true; + } + + statistics() { + const labels = {}; + const skeleton = { + rectangle: { + shape: 0, + track: 0, + }, + polygon: { + shape: 0, + track: 0, + }, + polyline: { + shape: 0, + track: 0, + }, + points: { + shape: 0, + track: 0, + }, + tags: 0, + manually: 0, + interpolated: 0, + total: 0, + }; + + const total = JSON.parse(JSON.stringify(skeleton)); + for (const label of Object.values(this.labels)) { + const { name } = label; + labels[name] = JSON.parse(JSON.stringify(skeleton)); + } + + for (const object of Object.values(this.objects)) { + 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 window.cvat.exceptions.ScriptingError( + `Unexpected object type: "${objectType}"`, + ); + } + + const label = object.label.name; + if (objectType === 'tag') { + labels[label].tags++; + labels[label].manually++; + labels[label].total++; + } else { + const { shapeType } = object; + labels[label][shapeType][objectType]++; + + if (objectType === 'track') { + const keyframes = Object.keys(object.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 = !object.shapes[keyframe].outside; + prevKeyframe = keyframe; + + if (visible) { + labels[label].manually++; + labels[label].total++; + } + } + } else { + labels[label].manually++; + labels[label].total++; + } + } + } + + for (const label of Object.keys(labels)) { + for (const key of Object.keys(labels[label])) { + if (typeof (labels[label][key]) === 'object') { + for (const objectType of Object.keys(labels[label][key])) { + total[key][objectType] += labels[label][key][objectType]; + } + } else { + total[key] += labels[label][key]; + } + } + } + + return new Statistics(labels, total); + } + + put() { + throw new window.cvat.exceptions.ScriptingError( + 'Is not implemented', + ); + } + + 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, window.cvat.classes.ObjectState); + if (state.outside) continue; + + const object = this.objects[state.clientID]; + if (typeof (object) === 'undefined') { + throw new window.cvat.exceptions.ArgumentError( + 'The object has not been saved yet. Call annotations.put(state) before', + ); + } + + const distance = object.constructor.distance(state.points, x, y); + if (distance !== null && (minimumDistance === null || distance < minimumDistance)) { + minimumDistance = distance; + minimumState = state; + } + } + + return { + state: minimumState, + distance: minimumDistance, + }; } } diff --git a/cvatjs/src/annotations-objects.js b/cvatjs/src/annotations-objects.js index 23d7e383d9ce..7057c1b55224 100644 --- a/cvatjs/src/annotations-objects.js +++ b/cvatjs/src/annotations-objects.js @@ -40,6 +40,8 @@ return attributeAccumulator; }, {}); this.appendDefaultAttributes(this.label); + + injection.groups.max = Math.max(injection.groups.max, this.group); } appendDefaultAttributes(label) { @@ -283,6 +285,11 @@ // Method is used to export data to the server toJSON() { + const labelAttributes = this.label.attributes.reduce((accumulator, attribute) => { + accumulator[attribute.id] = attribute; + return accumulator; + }, {}); + return { clientID: this.clientID, id: this.serverID, @@ -290,10 +297,12 @@ label_id: this.label.id, group: this.group, attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { - attributeAccumulator.push({ - spec_id: attrId, - value: this.attributes[attrId], - }); + if (!labelAttributes[attrId].mutable) { + attributeAccumulator.push({ + spec_id: attrId, + value: this.attributes[attrId], + }); + } return attributeAccumulator; }, []), @@ -306,10 +315,12 @@ outside: this.shapes[frame].outside, attributes: Object.keys(this.shapes[frame].attributes) .reduce((attributeAccumulator, attrId) => { - attributeAccumulator.push({ - spec_id: attrId, - value: this.shapes[frame].attributes[attrId], - }); + if (labelAttributes[attrId].mutable) { + attributeAccumulator.push({ + spec_id: attrId, + value: this.shapes[frame].attributes[attrId], + }); + } return attributeAccumulator; }, []), @@ -615,6 +626,19 @@ `No one neightbour frame found for the track with client ID: "${this.id}"`, ); } + + delete(force) { + if (!this.lock || force) { + this.removed = true; + this.resetCache(); + } + + return true; + } + + resetCache() { + this.cache = {}; + } } class Tag extends Annotation { @@ -720,6 +744,18 @@ super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE; } + + static distance(points, x, y) { + const [xtl, ytl, xbr, ybr] = points; + + if (!(x >= xtl && x <= xbr && y >= ytl && y <= ybr)) { + // Cursor is outside of a box + return null; + } + + // The shortest distance from point to an edge + return Math.min.apply(null, [x - xtl, y - ytl, xbr - x, ybr - y]); + } } class PolyShape extends Shape { @@ -733,6 +769,66 @@ super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POLYGON; } + + static distance(points, x, y) { + function position(x1, y1, x2, y2) { + return ((x1 - x) * (y2 - y) - (x2 - x) * (y1 - y)); + } + + let wn = 0; + const distances = []; + for (let i = 0; i < points.length; i += 2) { + // Current point + const x1 = points[i]; + const y1 = points[i + 1]; + + // Next point + const x2 = i + 2 < points.length ? points[i + 2] : points[0]; + const y2 = i + 3 < points.length ? points[i + 3] : points[1]; + + // Check if a point is inside a polygon + // with a winding numbers algorithm + // https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm + if (y1 <= y) { + if (y2 > y) { + if (position(x1, y1, x2, y2) > 0) { + wn++; + } + } + } else if (y2 < y) { + if (position(x1, y1, x2, y2) > 0) { + wn--; + } + } + + // Find the shortest distance from point to an edge + if (((x - x1) * (x2 - x)) >= 0 && ((y - y1) * (y2 - y)) >= 0) { + // Find the length of a perpendicular + // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + distances.push( + Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / Math + .sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)), + ); + } else { + // The link below works for lines (which have infinit length) + // There is a case when perpendicular doesn't cross the edge + // In this case we don't use the computed distance + // Instead we use just distance to the nearest point + distances.push( + Math.min( + Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)), + Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)), + ), + ); + } + } + + if (wn !== 0) { + return Math.min.apply(null, distances); + } + + return null; + } } class PolylineShape extends PolyShape { @@ -740,6 +836,42 @@ super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POLYLINE; } + + static distance(points, x, y) { + const distances = []; + for (let i = 0; i < points.length - 2; i += 2) { + // Current point + const x1 = points[i]; + const y1 = points[i + 1]; + + // Next point + const x2 = points[i + 2]; + const y2 = points[i + 3]; + + // Find the shortest distance from point to an edge + if (((x - x1) * (x2 - x)) >= 0 && ((y - y1) * (y2 - y)) >= 0) { + // Find the length of a perpendicular + // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + distances.push( + Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / Math + .sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)), + ); + } else { + // The link below works for lines (which have infinit length) + // There is a case when perpendicular doesn't cross the edge + // In this case we don't use the computed distance + // Instead we use just distance to the nearest point + distances.push( + Math.min( + Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)), + Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)), + ), + ); + } + } + + return Math.min.apply(null, distances); + } } class PointsShape extends PolyShape { @@ -747,6 +879,20 @@ super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POINTS; } + + static distance(points, x, y) { + const distances = []; + for (let i = 0; i < points.length; i += 2) { + const x1 = points[i]; + const y1 = points[i + 1]; + + distances.push( + Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)), + ); + } + + return Math.min.apply(null, distances); + } } class RectangleTrack extends Track { @@ -1181,6 +1327,11 @@ } } + RectangleTrack.distance = RectangleShape.distance; + PolygonTrack.distance = PolygonShape.distance; + PolylineTrack.distance = PolylineShape.distance; + PointsTrack.distance = PointsShape.distance; + module.exports = { RectangleShape, PolygonShape, diff --git a/cvatjs/src/annotations-saver.js b/cvatjs/src/annotations-saver.js index 5da4d753b62a..55bb100a3f3b 100644 --- a/cvatjs/src/annotations-saver.js +++ b/cvatjs/src/annotations-saver.js @@ -151,6 +151,10 @@ for (let i = 0; i < indexes[type].length; i++) { const clientID = indexes[type][i]; this.collection.objects[clientID].serverID = saved[type][i].id; + if (type === 'tracks') { + // We have to reset cache because of old value of serverID was saved there + this.collection.objects[clientID].resetCache(); + } } } } diff --git a/cvatjs/src/annotations.js b/cvatjs/src/annotations.js index 36160a5374c5..805d1c1ac222 100644 --- a/cvatjs/src/annotations.js +++ b/cvatjs/src/annotations.js @@ -29,7 +29,7 @@ ); } - async function getAnnotations(session, frame, filter) { + async function getAnnotationsFromServer(session) { const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); @@ -45,7 +45,12 @@ saver, }; } + } + async function getAnnotations(session, frame, filter) { + await getAnnotationsFromServer(session); + const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; + const cache = getCache(sessionType); return cache[session.id].collection.get(frame, filter); } @@ -69,7 +74,7 @@ } throw window.cvat.exceptions.DataError( - 'Collection has not been initialized yet. Call annotations.get() before', + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } @@ -82,20 +87,20 @@ } throw window.cvat.exceptions.DataError( - 'Collection has not been initialized yet. Call annotations.get() before', + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } - function groupAnnotations(session, objectStates) { + function groupAnnotations(session, objectStates, reset) { const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); if (session.id in cache) { - return cache[session.id].collection.group(objectStates); + return cache[session.id].collection.group(objectStates, reset); } throw window.cvat.exceptions.DataError( - 'Collection has not been initialized yet. Call annotations.get() before', + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } @@ -110,12 +115,69 @@ return false; } + async function clearAnnotations(session, reload) { + const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (session.id in cache) { + cache[session.id].collection.clear(); + } + + if (reload) { + delete cache[session.id]; + await getAnnotationsFromServer(session); + } + } + + function annotationsStatistics(session) { + const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (session.id in cache) { + return cache[session.id].collection.statistics(); + } + + throw window.cvat.exceptions.DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function putAnnotations(session, objectStates) { + const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (session.id in cache) { + return cache[session.id].collection.put(objectStates); + } + + throw window.cvat.exceptions.DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + + function selectObject(session, objectStates, x, y) { + const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (session.id in cache) { + return cache[session.id].collection.select(objectStates, x, y); + } + + throw window.cvat.exceptions.DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); + } + module.exports = { getAnnotations, + putAnnotations, saveAnnotations, hasUnsavedChanges, mergeAnnotations, splitAnnotations, groupAnnotations, + clearAnnotations, + annotationsStatistics, + selectObject, }; })(); diff --git a/cvatjs/src/common.js b/cvatjs/src/common.js index bd5b2e884684..b92d67ff9dfd 100644 --- a/cvatjs/src/common.js +++ b/cvatjs/src/common.js @@ -54,22 +54,20 @@ } throw new window.cvat.exceptions.ArgumentError( - `Got "${name}" value of type: "${typeof (value)}". ` - + `Expected "${type}"`, + `"${name}" is expected to be "${type}", but "${typeof (value)}" has been got.`, ); } } else if (instance) { if (!(value instanceof instance)) { if (value !== undefined) { throw new window.cvat.exceptions.ArgumentError( - `Got ${value.constructor.name} value for ${name}. ` - + `Expected instance of ${instance.name}`, + `${name} is expected to be ${instance.name}, but ` + + `"${value.constructor.name}" has been got`, ); } throw new window.cvat.exceptions.ArgumentError( - `Got undefined value for ${name}. ` - + `Expected instance of ${instance.name}`, + `"${name}" is expected to be ${instance.name}, but "undefined" has been got.`, ); } } diff --git a/cvatjs/src/session.js b/cvatjs/src/session.js index 939d2f552bb6..c9e1e75e99fd 100644 --- a/cvatjs/src/session.js +++ b/cvatjs/src/session.js @@ -13,11 +13,15 @@ const { getFrame } = require('./frames'); const { getAnnotations, + putAnnotations, saveAnnotations, hasUnsavedChanges, mergeAnnotations, splitAnnotations, groupAnnotations, + clearAnnotations, + selectObject, + annotationsStatistics, } = require('./annotations'); function buildDublicatedAPI(prototype) { @@ -36,9 +40,9 @@ return result; }, - async clear() { + async clear(reload = false) { const result = await PluginRegistry - .apiWrapper.call(this, prototype.annotations.clear); + .apiWrapper.call(this, prototype.annotations.clear, reload); return result; }, @@ -73,9 +77,10 @@ return result; }, - async select(frame, x, y) { + async select(objectStates, x, y) { const result = await PluginRegistry - .apiWrapper.call(this, prototype.annotations.select, frame, x, y); + .apiWrapper.call(this, + prototype.annotations.select, objectStates, x, y); return result; }, @@ -97,9 +102,10 @@ return result; }, - async group(objectStates) { + async group(objectStates, reset = false) { const result = await PluginRegistry - .apiWrapper.call(this, prototype.annotations.group, objectStates); + .apiWrapper.call(this, prototype.annotations.group, + objectStates, reset); return result; }, }, @@ -193,6 +199,10 @@ */ /** * 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} @@ -204,10 +214,14 @@ * Its argument is a text string */ /** - * Remove all annotations from a session + * 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 */ @@ -233,7 +247,7 @@ * @async */ /** - * Add some annotations to a session + * Create new objects from one-frame states * @method put * @memberof Session.annotations * @param {module:API.cvat.classes.ObjectState[]} data @@ -282,14 +296,16 @@ * @async */ /** - * Select shape under a cursor using smart alghorithms + * Select shape under a cursor using math alghorithms * @method select * @memberof Session.annotations - * @param {integer} frame frame for selecting + * @param {module:API.cvat.classes.ObjectState[]} objectStates + * object which can be selected * @param {float} x horizontal coordinate * @param {float} y vertical coordinate - * @returns {(integer|null)} - * an ID of a selected object or null if no one of objects is on position + * @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 @@ -327,6 +343,7 @@ * @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 {integer} an ID of created group * @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.PluginError} @@ -585,10 +602,14 @@ // So, we need return it this.annotations = { get: Object.getPrototypeOf(this).annotations.get.bind(this), + put: Object.getPrototypeOf(this).annotations.put.bind(this), save: Object.getPrototypeOf(this).annotations.save.bind(this), merge: Object.getPrototypeOf(this).annotations.merge.bind(this), split: Object.getPrototypeOf(this).annotations.split.bind(this), group: Object.getPrototypeOf(this).annotations.group.bind(this), + clear: Object.getPrototypeOf(this).annotations.clear.bind(this), + select: Object.getPrototypeOf(this).annotations.select.bind(this), + statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), hasUnsavedChanges: Object.getPrototypeOf(this) .annotations.hasUnsavedChanges.bind(this), }; @@ -665,23 +686,48 @@ }; Job.prototype.annotations.save.implementation = async function (onUpdate) { - await saveAnnotations(this, onUpdate); + const result = await saveAnnotations(this, onUpdate); + return result; }; Job.prototype.annotations.merge.implementation = async function (objectStates) { - await mergeAnnotations(this, objectStates); + const result = await mergeAnnotations(this, objectStates); + return result; }; Job.prototype.annotations.split.implementation = async function (objectState, frame) { - await splitAnnotations(this, objectState, frame); + const result = await splitAnnotations(this, objectState, frame); + return result; + }; + + Job.prototype.annotations.group.implementation = async function (objectStates, reset) { + const result = await groupAnnotations(this, objectStates, reset); + return result; + }; + + Job.prototype.annotations.hasUnsavedChanges.implementation = function () { + const result = hasUnsavedChanges(this); + return result; + }; + + Job.prototype.annotations.clear.implementation = async function (reload) { + const result = await clearAnnotations(this, reload); + return result; + }; + + Job.prototype.annotations.select.implementation = function (frame, x, y) { + const result = selectObject(this, frame, x, y); + return result; }; - Job.prototype.annotations.group.implementation = async function (objectStates) { - await groupAnnotations(this, objectStates); + Job.prototype.annotations.statistics.implementation = function () { + const result = annotationsStatistics(this); + return result; }; - Job.prototype.annotations.hasUnsavedChanges.implementation = async function () { - return hasUnsavedChanges(this); + Job.prototype.annotations.put.implementation = function (objectStates) { + const result = putAnnotations(this, objectStates); + return result; }; /** @@ -1090,10 +1136,14 @@ // So, we need return it this.annotations = { get: Object.getPrototypeOf(this).annotations.get.bind(this), + put: Object.getPrototypeOf(this).annotations.put.bind(this), save: Object.getPrototypeOf(this).annotations.save.bind(this), merge: Object.getPrototypeOf(this).annotations.merge.bind(this), split: Object.getPrototypeOf(this).annotations.split.bind(this), group: Object.getPrototypeOf(this).annotations.group.bind(this), + clear: Object.getPrototypeOf(this).annotations.clear.bind(this), + select: Object.getPrototypeOf(this).annotations.select.bind(this), + statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), hasUnsavedChanges: Object.getPrototypeOf(this) .annotations.hasUnsavedChanges.bind(this), }; @@ -1187,7 +1237,8 @@ }; Task.prototype.delete.implementation = async function () { - await serverProxy.tasks.deleteTask(this.id); + const result = await serverProxy.tasks.deleteTask(this.id); + return result; }; Task.prototype.frames.get.implementation = async function (frame) { @@ -1197,8 +1248,8 @@ ); } - const frameData = await getFrame(this.id, this.mode, frame); - return frameData; + const result = await getFrame(this.id, this.mode, frame); + return result; }; // TODO: Check filter for annotations @@ -1215,28 +1266,53 @@ ); } - const annotationsData = await getAnnotations(this, frame, filter); - return annotationsData; + const result = await getAnnotations(this, frame, filter); + return result; }; Task.prototype.annotations.save.implementation = async function (onUpdate) { - await saveAnnotations(this, onUpdate); + const result = await saveAnnotations(this, onUpdate); + return result; }; Task.prototype.annotations.merge.implementation = async function (objectStates) { - await mergeAnnotations(this, objectStates); + const result = await mergeAnnotations(this, objectStates); + return result; }; Task.prototype.annotations.split.implementation = async function (objectState, frame) { - await splitAnnotations(this, objectState, frame); + const result = await splitAnnotations(this, objectState, frame); + return result; + }; + + Task.prototype.annotations.group.implementation = async function (objectStates, reset) { + const result = await groupAnnotations(this, objectStates, reset); + return result; + }; + + Task.prototype.annotations.hasUnsavedChanges.implementation = function () { + const result = hasUnsavedChanges(this); + return result; + }; + + Task.prototype.annotations.clear.implementation = async function (reload) { + const result = await clearAnnotations(this, reload); + return result; + }; + + Task.prototype.annotations.select.implementation = function (frame, x, y) { + const result = selectObject(this, frame, x, y); + return result; }; - Task.prototype.annotations.group.implementation = async function (objectStates) { - await groupAnnotations(this, objectStates); + Task.prototype.annotations.statistics.implementation = function () { + const result = annotationsStatistics(this); + return result; }; - Task.prototype.annotations.hasUnsavedChanges.implementation = async function () { - return hasUnsavedChanges(this); + Task.prototype.annotations.put.implementation = function (objectStates) { + const result = putAnnotations(this, objectStates); + return result; }; module.exports = { diff --git a/cvatjs/src/statistics.js b/cvatjs/src/statistics.js index 3fd7b5b0d711..33704bc6d7ee 100644 --- a/cvatjs/src/statistics.js +++ b/cvatjs/src/statistics.js @@ -6,7 +6,7 @@ (() => { /** - * Class representing statistics inside a session + * Class representing collection statistics * @memberof module:API.cvat.classes * @hideconstructor */ @@ -14,27 +14,28 @@ constructor(label, total) { Object.defineProperties(this, Object.freeze({ /** - * Statistics by labels which have a structure: + * Statistics by labels with a structure: * @example * { - * label1: { + * label: { * boxes: { * tracks: 10, * shapes: 11, * }, * polygons: { - * tracks: 12, - * shapes: 13, + * tracks: 13, + * shapes: 14, * }, * polylines: { - * tracks: 14, - * shapes: 15, - * }, - * points: { * tracks: 16, * shapes: 17, * }, - * manually: 108, + * points: { + * tracks: 19, + * shapes: 20, + * }, + * tags: 66, + * manually: 186, * interpolated: 500, * total: 608, * } @@ -49,30 +50,29 @@ get: () => JSON.parse(JSON.stringify(label)), }, /** - * Total statistics (summed by all labels) which have a structure: + * Total statistics (covers all labels) with a structure: * @example * { - * total: { - * boxes: { + * boxes: { * tracks: 10, * shapes: 11, - * }, - * polygons: { - * tracks: 12, - * shapes: 13, - * }, - * polylines: { - * tracks: 14, - * shapes: 15, - * }, - * points: { - * tracks: 16, - * shapes: 17, - * }, - * manually: 108, - * interpolated: 500, - * total: 608, - * } + * }, + * polygons: { + * tracks: 13, + * shapes: 14, + * }, + * polylines: { + * tracks: 16, + * shapes: 17, + * }, + * points: { + * tracks: 19, + * shapes: 20, + * }, + * tags: 66, + * manually: 186, + * interpolated: 500, + * total: 608, * } * @name total * @type {Object}