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
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 0a2cb949bb
commit 6025151c6f

@ -23,6 +23,7 @@
objectStateFactory, objectStateFactory,
} = require('./annotations-objects'); } = require('./annotations-objects');
const { checkObjectType } = require('./common'); const { checkObjectType } = require('./common');
const Statistics = require('./statistics');
const colors = [ const colors = [
'#0066FF', '#AF593E', '#01A368', '#FF861F', '#ED0A3F', '#FF3F34', '#76D7EA', '#0066FF', '#AF593E', '#01A368', '#FF861F', '#ED0A3F', '#FF3F34', '#76D7EA',
@ -115,10 +116,14 @@
this.objects = {}; // key is a client id this.objects = {}; // key is a client id
this.count = 0; this.count = 0;
this.flush = false; 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 = { this.injection = {
labels: this.labels, labels: this.labels,
collectionZ: this.collectionZ, collectionZ: this.collectionZ,
groups: this.groups,
}; };
} }
@ -173,16 +178,6 @@
return data; return data;
} }
empty() {
this.shapes = {};
this.tags = {};
this.tracks = [];
this.objects = {}; // by id
this.count = 0;
this.flush = true;
}
get(frame) { get(frame) {
const { tracks } = this; const { tracks } = this;
const shapes = this.shapes[frame] || []; const shapes = this.shapes[frame] || [];
@ -206,7 +201,7 @@
} }
merge(objectStates) { merge(objectStates) {
checkObjectType('merged shapes', objectStates, null, Array); checkObjectType('shapes for merge', objectStates, null, Array);
if (!objectStates.length) return; if (!objectStates.length) return;
const objectsForMerge = objectStates.map((state) => { const objectsForMerge = objectStates.map((state) => {
checkObjectType('object state', state, null, window.cvat.classes.ObjectState); checkObjectType('object state', state, null, window.cvat.classes.ObjectState);
@ -364,30 +359,264 @@
}; };
const trackModel = trackFactory(track, clientID, this.injection); const trackModel = trackFactory(track, clientID, this.injection);
if (trackModel) { this.tracks.push(trackModel);
this.tracks.push(trackModel); this.objects[clientID] = trackModel;
this.objects[clientID] = trackModel;
}
// Remove other shapes // Remove other shapes
for (const object of objectsForMerge) { for (const object of objectsForMerge) {
object.removed = true; object.removed = true;
object.resetCache();
} }
} }
split(objectState) { split(objectState, frame) {
checkObjectType('object state', objectState, window.cvat.classes.ObjectState, null); 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) { group(objectStates, reset) {
checkObjectType('merged shapes', array, Array, null); checkObjectType('shapes for group', objectStates, null, Array);
for (const shape of array) {
checkObjectType('object state', shape, window.cvat.classes.ObjectState, null); 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,
};
} }
} }

@ -40,6 +40,8 @@
return attributeAccumulator; return attributeAccumulator;
}, {}); }, {});
this.appendDefaultAttributes(this.label); this.appendDefaultAttributes(this.label);
injection.groups.max = Math.max(injection.groups.max, this.group);
} }
appendDefaultAttributes(label) { appendDefaultAttributes(label) {
@ -283,6 +285,11 @@
// Method is used to export data to the server // Method is used to export data to the server
toJSON() { toJSON() {
const labelAttributes = this.label.attributes.reduce((accumulator, attribute) => {
accumulator[attribute.id] = attribute;
return accumulator;
}, {});
return { return {
clientID: this.clientID, clientID: this.clientID,
id: this.serverID, id: this.serverID,
@ -290,10 +297,12 @@
label_id: this.label.id, label_id: this.label.id,
group: this.group, group: this.group,
attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => {
attributeAccumulator.push({ if (!labelAttributes[attrId].mutable) {
spec_id: attrId, attributeAccumulator.push({
value: this.attributes[attrId], spec_id: attrId,
}); value: this.attributes[attrId],
});
}
return attributeAccumulator; return attributeAccumulator;
}, []), }, []),
@ -306,10 +315,12 @@
outside: this.shapes[frame].outside, outside: this.shapes[frame].outside,
attributes: Object.keys(this.shapes[frame].attributes) attributes: Object.keys(this.shapes[frame].attributes)
.reduce((attributeAccumulator, attrId) => { .reduce((attributeAccumulator, attrId) => {
attributeAccumulator.push({ if (labelAttributes[attrId].mutable) {
spec_id: attrId, attributeAccumulator.push({
value: this.shapes[frame].attributes[attrId], spec_id: attrId,
}); value: this.shapes[frame].attributes[attrId],
});
}
return attributeAccumulator; return attributeAccumulator;
}, []), }, []),
@ -615,6 +626,19 @@
`No one neightbour frame found for the track with client ID: "${this.id}"`, `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 { class Tag extends Annotation {
@ -720,6 +744,18 @@
super(data, clientID, color, injection); super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE; 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 { class PolyShape extends Shape {
@ -733,6 +769,66 @@
super(data, clientID, color, injection); super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYGON; 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 { class PolylineShape extends PolyShape {
@ -740,6 +836,42 @@
super(data, clientID, color, injection); super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POLYLINE; 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 { class PointsShape extends PolyShape {
@ -747,6 +879,20 @@
super(data, clientID, color, injection); super(data, clientID, color, injection);
this.shapeType = window.cvat.enums.ObjectShape.POINTS; 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 { 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 = { module.exports = {
RectangleShape, RectangleShape,
PolygonShape, PolygonShape,

@ -151,6 +151,10 @@
for (let i = 0; i < indexes[type].length; i++) { for (let i = 0; i < indexes[type].length; i++) {
const clientID = indexes[type][i]; const clientID = indexes[type][i];
this.collection.objects[clientID].serverID = saved[type][i].id; 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();
}
} }
} }
} }

@ -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 sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType); const cache = getCache(sessionType);
@ -45,7 +45,12 @@
saver, 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); return cache[session.id].collection.get(frame, filter);
} }
@ -69,7 +74,7 @@
} }
throw window.cvat.exceptions.DataError( 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( 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 sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job';
const cache = getCache(sessionType); const cache = getCache(sessionType);
if (session.id in cache) { 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( 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; 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 = { module.exports = {
getAnnotations, getAnnotations,
putAnnotations,
saveAnnotations, saveAnnotations,
hasUnsavedChanges, hasUnsavedChanges,
mergeAnnotations, mergeAnnotations,
splitAnnotations, splitAnnotations,
groupAnnotations, groupAnnotations,
clearAnnotations,
annotationsStatistics,
selectObject,
}; };
})(); })();

@ -54,22 +54,20 @@
} }
throw new window.cvat.exceptions.ArgumentError( throw new window.cvat.exceptions.ArgumentError(
`Got "${name}" value of type: "${typeof (value)}". ` `"${name}" is expected to be "${type}", but "${typeof (value)}" has been got.`,
+ `Expected "${type}"`,
); );
} }
} else if (instance) { } else if (instance) {
if (!(value instanceof instance)) { if (!(value instanceof instance)) {
if (value !== undefined) { if (value !== undefined) {
throw new window.cvat.exceptions.ArgumentError( throw new window.cvat.exceptions.ArgumentError(
`Got ${value.constructor.name} value for ${name}. ` `${name} is expected to be ${instance.name}, but `
+ `Expected instance of ${instance.name}`, + `"${value.constructor.name}" has been got`,
); );
} }
throw new window.cvat.exceptions.ArgumentError( throw new window.cvat.exceptions.ArgumentError(
`Got undefined value for ${name}. ` `"${name}" is expected to be ${instance.name}, but "undefined" has been got.`,
+ `Expected instance of ${instance.name}`,
); );
} }
} }

@ -13,11 +13,15 @@
const { getFrame } = require('./frames'); const { getFrame } = require('./frames');
const { const {
getAnnotations, getAnnotations,
putAnnotations,
saveAnnotations, saveAnnotations,
hasUnsavedChanges, hasUnsavedChanges,
mergeAnnotations, mergeAnnotations,
splitAnnotations, splitAnnotations,
groupAnnotations, groupAnnotations,
clearAnnotations,
selectObject,
annotationsStatistics,
} = require('./annotations'); } = require('./annotations');
function buildDublicatedAPI(prototype) { function buildDublicatedAPI(prototype) {
@ -36,9 +40,9 @@
return result; return result;
}, },
async clear() { async clear(reload = false) {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper.call(this, prototype.annotations.clear); .apiWrapper.call(this, prototype.annotations.clear, reload);
return result; return result;
}, },
@ -73,9 +77,10 @@
return result; return result;
}, },
async select(frame, x, y) { async select(objectStates, x, y) {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper.call(this, prototype.annotations.select, frame, x, y); .apiWrapper.call(this,
prototype.annotations.select, objectStates, x, y);
return result; return result;
}, },
@ -97,9 +102,10 @@
return result; return result;
}, },
async group(objectStates) { async group(objectStates, reset = false) {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper.call(this, prototype.annotations.group, objectStates); .apiWrapper.call(this, prototype.annotations.group,
objectStates, reset);
return result; return result;
}, },
}, },
@ -193,6 +199,10 @@
*/ */
/** /**
* Save all changes in annotations on a server * 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 * @method save
* @memberof Session.annotations * @memberof Session.annotations
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
@ -204,10 +214,14 @@
* Its argument is a text string * Its argument is a text string
*/ */
/** /**
* Remove all annotations from a session * Remove all annotations and optionally reinitialize it
* @method clear * @method clear
* @memberof Session.annotations * @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.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @throws {module:API.cvat.exceptions.ServerError}
* @instance * @instance
* @async * @async
*/ */
@ -233,7 +247,7 @@
* @async * @async
*/ */
/** /**
* Add some annotations to a session * Create new objects from one-frame states
* @method put * @method put
* @memberof Session.annotations * @memberof Session.annotations
* @param {module:API.cvat.classes.ObjectState[]} data * @param {module:API.cvat.classes.ObjectState[]} data
@ -282,14 +296,16 @@
* @async * @async
*/ */
/** /**
* Select shape under a cursor using smart alghorithms * Select shape under a cursor using math alghorithms
* @method select * @method select
* @memberof Session.annotations * @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} x horizontal coordinate
* @param {float} y vertical coordinate * @param {float} y vertical coordinate
* @returns {(integer|null)} * @returns {Object}
* an ID of a selected object or null if no one of objects is on position * 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.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
* @instance * @instance
@ -327,6 +343,7 @@
* @method group * @method group
* @memberof Session.annotations * @memberof Session.annotations
* @param {module:API.cvat.classes.ObjectState[]} objectStates * @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 * @returns {integer} an ID of created group
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
@ -585,10 +602,14 @@
// So, we need return it // So, we need return it
this.annotations = { this.annotations = {
get: Object.getPrototypeOf(this).annotations.get.bind(this), get: Object.getPrototypeOf(this).annotations.get.bind(this),
put: Object.getPrototypeOf(this).annotations.put.bind(this),
save: Object.getPrototypeOf(this).annotations.save.bind(this), save: Object.getPrototypeOf(this).annotations.save.bind(this),
merge: Object.getPrototypeOf(this).annotations.merge.bind(this), merge: Object.getPrototypeOf(this).annotations.merge.bind(this),
split: Object.getPrototypeOf(this).annotations.split.bind(this), split: Object.getPrototypeOf(this).annotations.split.bind(this),
group: Object.getPrototypeOf(this).annotations.group.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) hasUnsavedChanges: Object.getPrototypeOf(this)
.annotations.hasUnsavedChanges.bind(this), .annotations.hasUnsavedChanges.bind(this),
}; };
@ -665,23 +686,48 @@
}; };
Job.prototype.annotations.save.implementation = async function (onUpdate) { 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) { 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) { 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) { Job.prototype.annotations.statistics.implementation = function () {
await groupAnnotations(this, objectStates); const result = annotationsStatistics(this);
return result;
}; };
Job.prototype.annotations.hasUnsavedChanges.implementation = async function () { Job.prototype.annotations.put.implementation = function (objectStates) {
return hasUnsavedChanges(this); const result = putAnnotations(this, objectStates);
return result;
}; };
/** /**
@ -1090,10 +1136,14 @@
// So, we need return it // So, we need return it
this.annotations = { this.annotations = {
get: Object.getPrototypeOf(this).annotations.get.bind(this), get: Object.getPrototypeOf(this).annotations.get.bind(this),
put: Object.getPrototypeOf(this).annotations.put.bind(this),
save: Object.getPrototypeOf(this).annotations.save.bind(this), save: Object.getPrototypeOf(this).annotations.save.bind(this),
merge: Object.getPrototypeOf(this).annotations.merge.bind(this), merge: Object.getPrototypeOf(this).annotations.merge.bind(this),
split: Object.getPrototypeOf(this).annotations.split.bind(this), split: Object.getPrototypeOf(this).annotations.split.bind(this),
group: Object.getPrototypeOf(this).annotations.group.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) hasUnsavedChanges: Object.getPrototypeOf(this)
.annotations.hasUnsavedChanges.bind(this), .annotations.hasUnsavedChanges.bind(this),
}; };
@ -1187,7 +1237,8 @@
}; };
Task.prototype.delete.implementation = async function () { 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) { Task.prototype.frames.get.implementation = async function (frame) {
@ -1197,8 +1248,8 @@
); );
} }
const frameData = await getFrame(this.id, this.mode, frame); const result = await getFrame(this.id, this.mode, frame);
return frameData; return result;
}; };
// TODO: Check filter for annotations // TODO: Check filter for annotations
@ -1215,28 +1266,53 @@
); );
} }
const annotationsData = await getAnnotations(this, frame, filter); const result = await getAnnotations(this, frame, filter);
return annotationsData; return result;
}; };
Task.prototype.annotations.save.implementation = async function (onUpdate) { 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) { 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) { 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) { Task.prototype.annotations.statistics.implementation = function () {
await groupAnnotations(this, objectStates); const result = annotationsStatistics(this);
return result;
}; };
Task.prototype.annotations.hasUnsavedChanges.implementation = async function () { Task.prototype.annotations.put.implementation = function (objectStates) {
return hasUnsavedChanges(this); const result = putAnnotations(this, objectStates);
return result;
}; };
module.exports = { module.exports = {

@ -6,7 +6,7 @@
(() => { (() => {
/** /**
* Class representing statistics inside a session * Class representing collection statistics
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
@ -14,27 +14,28 @@
constructor(label, total) { constructor(label, total) {
Object.defineProperties(this, Object.freeze({ Object.defineProperties(this, Object.freeze({
/** /**
* Statistics by labels which have a structure: * Statistics by labels with a structure:
* @example * @example
* { * {
* label1: { * label: {
* boxes: { * boxes: {
* tracks: 10, * tracks: 10,
* shapes: 11, * shapes: 11,
* }, * },
* polygons: { * polygons: {
* tracks: 12, * tracks: 13,
* shapes: 13, * shapes: 14,
* }, * },
* polylines: { * polylines: {
* tracks: 14,
* shapes: 15,
* },
* points: {
* tracks: 16, * tracks: 16,
* shapes: 17, * shapes: 17,
* }, * },
* manually: 108, * points: {
* tracks: 19,
* shapes: 20,
* },
* tags: 66,
* manually: 186,
* interpolated: 500, * interpolated: 500,
* total: 608, * total: 608,
* } * }
@ -49,30 +50,29 @@
get: () => JSON.parse(JSON.stringify(label)), 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 * @example
* { * {
* total: { * boxes: {
* boxes: {
* tracks: 10, * tracks: 10,
* shapes: 11, * shapes: 11,
* }, * },
* polygons: { * polygons: {
* tracks: 12, * tracks: 13,
* shapes: 13, * shapes: 14,
* }, * },
* polylines: { * polylines: {
* tracks: 14, * tracks: 16,
* shapes: 15, * shapes: 17,
* }, * },
* points: { * points: {
* tracks: 16, * tracks: 19,
* shapes: 17, * shapes: 20,
* }, * },
* manually: 108, * tags: 66,
* interpolated: 500, * manually: 186,
* total: 608, * interpolated: 500,
* } * total: 608,
* } * }
* @name total * @name total
* @type {Object} * @type {Object}

Loading…
Cancel
Save