CVAT.js: Save and delete for shapes/tracks/tags (#555)

main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 5104cc08c1
commit 22bcf1cf84

@ -48,5 +48,6 @@
"indent": ["warn", 4], "indent": ["warn", 4],
"no-useless-constructor": 0, "no-useless-constructor": 0,
"func-names": [0], "func-names": [0],
"valid-typeof": [0],
}, },
}; };

@ -11,17 +11,85 @@
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const ObjectState = require('./object-state'); const ObjectState = require('./object-state');
function objectStateFactory(frame, data) {
const objectState = new ObjectState(data);
// Rewrite default implementations of save/delete
objectState.updateInCollection = this.save.bind(this, frame, objectState);
objectState.deleteFromCollection = this.delete.bind(this);
return objectState;
}
function checkObjectType(name, value, type, instance) {
if (type) {
if (typeof (value) !== type) {
// specific case for integers which aren't native type in JS
if (type === 'integer' && Number.isInteger(value)) {
return;
}
if (value !== undefined) {
throw new window.cvat.exceptions.ArgumentError(
`Got ${typeof (value)} value for ${name}. `
+ `Expected ${type}`,
);
}
throw new window.cvat.exceptions.ArgumentError(
`Got undefined value for ${name}. `
+ `Expected ${type}`,
);
}
} 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}`,
);
}
throw new window.cvat.exceptions.ArgumentError(
`Got undefined value for ${name}. `
+ `Expected instance of ${instance.name}`,
);
}
}
}
class Annotation { class Annotation {
constructor(data, clientID, injection) { constructor(data, clientID, injection) {
this.taskLabels = injection.labels;
this.clientID = clientID; this.clientID = clientID;
this.serverID = data.id; this.serverID = data.id;
this.labelID = data.label_id; this.group = data.group;
this.label = this.taskLabels[data.label_id];
this.frame = data.frame; this.frame = data.frame;
this.removed = false;
this.lock = false;
this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { this.attributes = data.attributes.reduce((attributeAccumulator, attr) => {
attributeAccumulator[attr.spec_id] = attr.value; attributeAccumulator[attr.spec_id] = attr.value;
return attributeAccumulator; return attributeAccumulator;
}, {}); }, {});
this.taskLabels = injection.labels; this.appendDefaultAttributes(this.label);
}
appendDefaultAttributes(label) {
const labelAttributes = label.attributes;
for (const attribute of labelAttributes) {
if (!(attribute.id in this.attributes)) {
this.attributes[attribute.id] = attribute.defaultValue;
}
}
}
delete(force) {
if (!this.lock || force) {
this.removed = true;
}
return true;
} }
} }
@ -31,11 +99,11 @@
this.points = data.points; this.points = data.points;
this.occluded = data.occluded; this.occluded = data.occluded;
this.zOrder = data.z_order; this.zOrder = data.z_order;
this.group = data.group;
this.color = color; this.color = color;
this.shape = null; this.shape = null;
} }
// Method is used to export data to the server
toJSON() { toJSON() {
return { return {
occluded: this.occluded, occluded: this.occluded,
@ -51,11 +119,12 @@
}, []), }, []),
id: this.serverID, id: this.serverID,
frame: this.frame, frame: this.frame,
label_id: this.labelID, label_id: this.label.id,
group: this.group, group: this.group,
}; };
} }
// Method is used to construct ObjectState objects
get(frame) { get(frame) {
if (frame !== this.frame) { if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError( throw new window.cvat.exceptions.ScriptingError(
@ -68,13 +137,99 @@
shape: this.shape, shape: this.shape,
clientID: this.clientID, clientID: this.clientID,
occluded: this.occluded, occluded: this.occluded,
lock: this.lock,
zOrder: this.zOrder, zOrder: this.zOrder,
points: [...this.points], points: [...this.points],
attributes: Object.assign({}, this.attributes), attributes: Object.assign({}, this.attributes),
label: this.taskLabels[this.labelID], label: this.label,
group: this.group, group: this.group,
color: this.color,
}; };
} }
save(frame, data) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
if (this.lock && data.lock) {
return objectStateFactory.call(this, frame, this.get(frame));
}
// All changes are done in this temporary object
const copy = this.get(frame);
const updated = data.updateFlags;
if (updated.label) {
checkObjectType('label', data.label, null, window.cvat.classes.Label);
copy.label = data.label;
copy.attributes = {};
this.appendDefaultAttributes.call(copy, copy.label);
}
if (updated.attributes) {
const labelAttributes = copy.label
.attributes.map(attr => `${attr.id}`);
for (const attrID of Object.keys(data.attributes)) {
if (labelAttributes.includes(attrID)) {
copy.attributes[attrID] = data.attributes[attrID];
}
}
}
if (updated.points) {
checkObjectType('points', data.points, null, Array);
copy.points = [];
for (const coordinate of data.points) {
checkObjectType('coordinate', coordinate, 'number', null);
copy.points.push(coordinate);
}
}
if (updated.occluded) {
checkObjectType('occluded', data.occluded, 'boolean', null);
copy.occluded = data.occluded;
}
if (updated.group) {
checkObjectType('group', data.group, 'integer', null);
copy.group = data.group;
}
if (updated.zOrder) {
checkObjectType('zOrder', data.zOrder, 'integer', null);
copy.zOrder = data.zOrder;
}
if (updated.lock) {
checkObjectType('lock', data.lock, 'boolean', null);
copy.lock = data.lock;
}
if (updated.color) {
checkObjectType('color', data.color, 'string', null);
if (/^#[0-9A-F]{6}$/i.test(data.color)) {
throw new window.cvat.exceptions.ArgumentError(
`Got invalid color value: "${data.color}"`,
);
}
copy.color = data.color;
}
// Reset flags and commit all changes
updated.reset();
for (const prop of Object.keys(copy)) {
if (prop in this) {
this[prop] = copy[prop];
}
}
return objectStateFactory.call(this, frame, this.get(frame));
}
} }
class Track extends Annotation { class Track extends Annotation {
@ -86,7 +241,6 @@
occluded: value.occluded, occluded: value.occluded,
zOrder: value.z_order, zOrder: value.z_order,
points: value.points, points: value.points,
id: value.id,
frame: value.frame, frame: value.frame,
outside: value.outside, outside: value.outside,
attributes: value.attributes.reduce((attributeAccumulator, attr) => { attributes: value.attributes.reduce((attributeAccumulator, attr) => {
@ -98,15 +252,17 @@
return shapeAccumulator; return shapeAccumulator;
}, {}); }, {});
this.group = data.group;
this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { this.attributes = data.attributes.reduce((attributeAccumulator, attr) => {
attributeAccumulator[attr.spec_id] = attr.value; attributeAccumulator[attr.spec_id] = attr.value;
return attributeAccumulator; return attributeAccumulator;
}, {}); }, {});
this.cache = {};
this.color = color; this.color = color;
this.shape = null; this.shape = null;
} }
// Method is used to export data to the server
toJSON() { toJSON() {
return { return {
occluded: this.occluded, occluded: this.occluded,
@ -123,7 +279,7 @@
id: this.serverID, id: this.serverID,
frame: this.frame, frame: this.frame,
label_id: this.labelID, label_id: this.label.id,
group: this.group, group: this.group,
shapes: Object.keys(this.shapes).reduce((shapesAccumulator, frame) => { shapes: Object.keys(this.shapes).reduce((shapesAccumulator, frame) => {
shapesAccumulator.push({ shapesAccumulator.push({
@ -150,18 +306,27 @@
}; };
} }
get(targetFrame) { // Method is used to construct ObjectState objects
return Object.assign( get(frame) {
{}, this.getPosition(targetFrame), if (!(frame in this.cache)) {
const interpolation = Object.assign(
{}, this.getPosition(frame),
{ {
attributes: this.getAttributes(targetFrame), attributes: this.getAttributes(frame),
label: this.taskLabels[this.labelID], label: this.label,
group: this.group, group: this.group,
type: window.cvat.enums.ObjectType.TRACK, type: window.cvat.enums.ObjectType.TRACK,
shape: this.shape, shape: this.shape,
clientID: this.clientID, clientID: this.clientID,
lock: this.lock,
color: this.color,
}, },
); );
this.cache[frame] = interpolation;
}
return JSON.parse(JSON.stringify(this.cache[frame]));
} }
neighborsFrames(targetFrame) { neighborsFrames(targetFrame) {
@ -211,20 +376,173 @@
} }
} }
// Finally fill up remained attributes if they exist return result;
const labelAttributes = this.taskLabels[this.labelID].attributes; }
const defValuesByID = labelAttributes.reduce((accumulator, attr) => {
accumulator[attr.id] = attr.defaultValue; save(frame, data) {
if (this.lock || data.lock) {
this.lock = data.lock;
return objectStateFactory.call(this, frame, this.get(frame));
}
// All changes are done in this temporary object
const copy = Object.assign(this.get(frame));
copy.attributes = Object.assign(copy.attributes);
copy.points = [...copy.points];
const updated = data.updateFlags;
let positionUpdated = false;
if (updated.label) {
checkObjectType('label', data.label, null, window.cvat.classes.Label);
copy.label = data.label;
copy.attributes = {};
// Shape attributes will be removed later after all checks
this.appendDefaultAttributes.call(copy, copy.label);
}
if (updated.attributes) {
const labelAttributes = copy.label.attributes
.reduce((accumulator, value) => {
accumulator[value.id] = value;
return accumulator; return accumulator;
}, {}); }, {});
for (const attrID of Object.keys(defValuesByID)) { for (const attrID of Object.keys(data.attributes)) {
if (!(attrID in result)) { if (attrID in labelAttributes) {
result[attrID] = defValuesByID[attrID]; copy.attributes[attrID] = data.attributes[attrID];
if (!labelAttributes[attrID].mutable) {
this.attributes[attrID] = data.attributes[attrID];
} else {
// Mutable attributes will be updated later
positionUpdated = true;
}
}
} }
} }
return result; if (updated.points) {
checkObjectType('points', data.points, null, Array);
copy.points = [];
for (const coordinate of data.points) {
checkObjectType('coordinate', coordinate, 'number', null);
copy.points.push(coordinate);
}
positionUpdated = true;
}
if (updated.occluded) {
checkObjectType('occluded', data.occluded, 'boolean', null);
copy.occluded = data.occluded;
positionUpdated = true;
}
if (updated.outside) {
checkObjectType('outside', data.outside, 'boolean', null);
copy.outside = data.outside;
positionUpdated = true;
}
if (updated.group) {
checkObjectType('group', data.group, 'integer', null);
copy.group = data.group;
}
if (updated.zOrder) {
checkObjectType('zOrder', data.zOrder, 'integer', null);
copy.zOrder = data.zOrder;
positionUpdated = true;
}
if (updated.lock) {
checkObjectType('lock', data.lock, 'boolean', null);
copy.lock = data.lock;
}
if (updated.color) {
checkObjectType('color', data.color, 'string', null);
if (/^#[0-9A-F]{6}$/i.test(data.color)) {
throw new window.cvat.exceptions.ArgumentError(
`Got invalid color value: "${data.color}"`,
);
}
copy.color = data.color;
}
// Commit all changes
for (const prop of Object.keys(copy)) {
if (prop in this) {
this[prop] = copy[prop];
}
this.cache[frame][prop] = copy[prop];
}
if (updated.label) {
for (const shape of this.shapes) {
shape.attributes = {};
}
}
// Remove keyframe
if (updated.keyframe && !data.keyframe) {
// Remove all cache after this keyframe because it have just become outdated
for (const cacheFrame in this.cache) {
if (+cacheFrame > frame) {
delete this.cache[frame];
}
}
this.cache[frame].keyframe = false;
delete this.shapes[frame];
updated.reset();
return objectStateFactory.call(this, frame, this.get(frame));
}
// Add/update keyframe
if (positionUpdated || (updated.keyframe && data.keyframe)) {
// Remove all cache after this keyframe because it have just become outdated
for (const cacheFrame in this.cache) {
if (+cacheFrame > frame) {
delete this.cache[frame];
}
}
this.cache[frame].keyframe = true;
data.keyframe = true;
this.shapes[frame] = {
frame,
zOrder: copy.zOrder,
points: copy.points,
outside: copy.outside,
occluded: copy.occluded,
attributes: {},
};
if (updated.attributes) {
const labelAttributes = this.label.attributes
.reduce((accumulator, value) => {
accumulator[value.id] = value;
return accumulator;
}, {});
// Unmutable attributes were updated above
for (const attrID of Object.keys(data.attributes)) {
if (attrID in labelAttributes && labelAttributes[attrID].mutable) {
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
this.shapes[frame].attributes[attrID] = data.attributes[attrID];
}
}
}
}
updated.reset();
return objectStateFactory.call(this, frame, this.get(frame));
} }
getPosition(targetFrame) { getPosition(targetFrame) {
@ -242,15 +560,18 @@
occluded: leftPosition.occluded, occluded: leftPosition.occluded,
outside: leftPosition.outside, outside: leftPosition.outside,
zOrder: leftPosition.zOrder, zOrder: leftPosition.zOrder,
keyframe: true,
}; };
} }
if (rightPosition && leftPosition) { if (rightPosition && leftPosition) {
return this.interpolatePosition( return Object.assign({}, this.interpolatePosition(
leftPosition, leftPosition,
rightPosition, rightPosition,
targetFrame, targetFrame,
); ), {
keyframe: false,
});
} }
if (rightPosition) { if (rightPosition) {
@ -259,6 +580,7 @@
occluded: rightPosition.occluded, occluded: rightPosition.occluded,
outside: true, outside: true,
zOrder: 0, zOrder: 0,
keyframe: false,
}; };
} }
@ -268,6 +590,7 @@
occluded: leftPosition.occluded, occluded: leftPosition.occluded,
outside: leftPosition.outside, outside: leftPosition.outside,
zOrder: 0, zOrder: 0,
keyframe: false,
}; };
} }
@ -282,17 +605,94 @@
super(data, clientID, injection); super(data, clientID, injection);
} }
// Method is used to export data to the server
toJSON() { toJSON() {
// TODO: Tags support return {
return {}; id: this.serverID,
frame: this.frame,
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],
});
return attributeAccumulator;
}, []),
};
} }
// Method is used to construct ObjectState objects
get(frame) { get(frame) {
if (frame !== this.frame) { if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError( throw new window.cvat.exceptions.ScriptingError(
'Got frame is not equal to the frame of the shape', 'Got frame is not equal to the frame of the shape',
); );
} }
return {
type: window.cvat.enums.ObjectType.TAG,
clientID: this.clientID,
lock: this.lock,
attributes: Object.assign({}, this.attributes),
label: this.label,
group: this.group,
};
}
save(frame, data) {
if (frame !== this.frame) {
throw new window.cvat.exceptions.ScriptingError(
'Got frame is not equal to the frame of the shape',
);
}
if (this.lock && data.lock) {
return objectStateFactory.call(this, frame, this.get(frame));
}
// All changes are done in this temporary object
const copy = this.get(frame);
const updated = data.updateFlags;
if (updated.label) {
checkObjectType('label', data.label, null, window.cvat.classes.Label);
copy.label = data.label;
copy.attributes = {};
this.appendDefaultAttributes.call(copy, copy.label);
}
if (updated.attributes) {
const labelAttributes = copy.label
.attributes.map(attr => `${attr.id}`);
for (const attrID of Object.keys(data.attributes)) {
if (labelAttributes.includes(attrID)) {
copy.attributes[attrID] = data.attributes[attrID];
}
}
}
if (updated.group) {
checkObjectType('group', data.group, 'integer', null);
copy.group = data.group;
}
if (updated.lock) {
checkObjectType('lock', data.lock, 'boolean', null);
copy.lock = data.lock;
}
// Reset flags and commit all changes
updated.reset();
for (const prop of Object.keys(copy)) {
if (prop in this) {
this[prop] = copy[prop];
}
}
return objectStateFactory.call(this, frame, this.get(frame));
} }
} }
@ -915,15 +1315,12 @@
const shapes = this.shapes[frame] || []; const shapes = this.shapes[frame] || [];
const tags = this.tags[frame] || []; const tags = this.tags[frame] || [];
const states = tracks.map(track => track.get(frame)) const objects = tracks.concat(shapes).concat(tags).filter(object => !object.removed);
.concat(shapes.map(shape => shape.get(frame)))
.concat(tags.map(tag => tag.get(frame)));
// filtering here // filtering here
const objectStates = []; const objectStates = [];
for (const state of states) { for (const object of objects) {
const objectState = new ObjectState(state); const objectState = objectStateFactory.call(object, frame, object.get(frame));
objectStates.push(objectState); objectStates.push(objectState);
} }

@ -116,7 +116,7 @@
if ('taskID' in filter) { if ('taskID' in filter) {
task = await serverProxy.tasks.getTasks(`id=${filter.taskID}`); task = await serverProxy.tasks.getTasks(`id=${filter.taskID}`);
} else { } else {
const [job] = await serverProxy.jobs.getJob(filter.jobID); const job = await serverProxy.jobs.getJob(filter.jobID);
task = await serverProxy.tasks.getTasks(`id=${job.task_id}`); task = await serverProxy.tasks.getTasks(`id=${job.task_id}`);
} }

@ -16,25 +16,69 @@
*/ */
class ObjectState { class ObjectState {
/** /**
* @param {Object} type - an object which contains initialization information * @param {Object} serialized - is an dictionary which contains
* about points, group, zOrder, outside, occluded, * initial information about an ObjectState;
* attributes, lock, type, label, mode, etc. * Necessary fields: type, shape
* Types of data equal to listed below * Necessary fields for objects which haven't been added to collection yet: frame
* Optional fields: points, group, zOrder, outside, occluded,
* attributes, lock, label, mode, color, keyframe
* These fields can be set later via setters
*/ */
constructor(serialized) { constructor(serialized) {
const data = { const data = {
label: null,
attributes: {},
points: null, points: null,
group: null,
zOrder: null,
outside: null, outside: null,
occluded: null, occluded: null,
keyframe: null,
group: null,
zOrder: null,
lock: null, lock: null,
attributes: {}, color: null,
frame: serialized.frame,
type: serialized.type, type: serialized.type,
shape: serialized.shape, shape: serialized.shape,
updateFlags: {},
}; };
// Shows whether any properties updated since last reset() or interpolation
Object.defineProperty(data.updateFlags, 'reset', {
value: function reset() {
this.label = false;
this.attributes = false;
this.points = false;
this.outside = false;
this.occluded = false;
this.keyframe = false;
this.group = false;
this.zOrder = false;
this.lock = false;
this.color = false;
},
writable: false,
});
Object.defineProperties(this, Object.freeze({ Object.defineProperties(this, Object.freeze({
// Internal property. We don't need document it.
updateFlags: {
get: () => data.updateFlags,
},
frame: {
/**
* @name frame
* @type {integer}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.frame,
},
type: { type: {
/** /**
* @name type * @name type
@ -61,51 +105,37 @@
* @type {module:API.cvat.classes.Label} * @type {module:API.cvat.classes.Label}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
get: () => data.label, get: () => data.label,
set: (labelInstance) => { set: (labelInstance) => {
if (!(labelInstance instanceof window.cvat.classes.Label)) { data.updateFlags.label = true;
throw new window.cvat.exceptions.ArgumentError(
`Expected Label instance, but got "${typeof (labelInstance.constructor.name)}"`,
);
}
data.label = labelInstance; data.label = labelInstance;
}, },
}, },
points: { color: {
/** /**
* @typedef {Object} Point * @name color
* @property {number} x * @type {string}
* @property {number} y * @memberof module:API.cvat.classes.ObjectState
* @global * @instance
*/ */
get: () => data.color,
set: (color) => {
data.updateFlags.color = true;
data.color = color;
},
},
points: {
/** /**
* @name points * @name points
* @type {Point[]} * @type {number[]}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
get: () => data.position, get: () => data.points,
set: (position) => { set: (points) => {
if (Array.isArray(position)) { data.updateFlags.points = true;
for (const point of position) { data.points = [...points];
if (typeof (point) !== 'object'
|| !('x' in point) || !('y' in point)) {
throw new window.cvat.exceptions.ArgumentError(
`Got invalid point ${point}`,
);
}
}
} else {
throw new window.cvat.exceptions.ArgumentError(
`Got invalid type "${typeof (position.constructor.name)}"`,
);
}
data.position = position;
}, },
}, },
group: { group: {
@ -114,17 +144,11 @@
* @type {integer} * @type {integer}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
get: () => data.group, get: () => data.group,
set: (groupID) => { set: (group) => {
if (!Number.isInteger(groupID)) { data.updateFlags.group = true;
throw new window.cvat.exceptions.ArgumentError( data.group = group;
`Expected integer, but got ${groupID.constructor.name}`,
);
}
data.group = groupID;
}, },
}, },
zOrder: { zOrder: {
@ -133,16 +157,10 @@
* @type {integer} * @type {integer}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
get: () => data.zOrder, get: () => data.zOrder,
set: (zOrder) => { set: (zOrder) => {
if (!Number.isInteger(zOrder)) { data.updateFlags.zOrder = true;
throw new window.cvat.exceptions.ArgumentError(
`Expected integer, but got ${zOrder.constructor.name}`,
);
}
data.zOrder = zOrder; data.zOrder = zOrder;
}, },
}, },
@ -152,35 +170,36 @@
* @type {boolean} * @type {boolean}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
get: () => data.outside, get: () => data.outside,
set: (outside) => { set: (outside) => {
if (typeof (outside) !== 'boolean') { data.updateFlags.outside = true;
throw new window.cvat.exceptions.ArgumentError(
`Expected boolean, but got ${outside.constructor.name}`,
);
}
data.outside = outside; data.outside = outside;
}, },
}, },
keyframe: {
/**
* @name keyframe
* @type {boolean}
* @memberof module:API.cvat.classes.ObjectState
* @instance
*/
get: () => data.keyframe,
set: (keyframe) => {
data.updateFlags.keyframe = true;
data.keyframe = keyframe;
},
},
occluded: { occluded: {
/** /**
* @name occluded * @name occluded
* @type {boolean} * @type {boolean}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
get: () => data.occluded, get: () => data.occluded,
set: (occluded) => { set: (occluded) => {
if (typeof (occluded) !== 'boolean') { data.updateFlags.occluded = true;
throw new window.cvat.exceptions.ArgumentError(
`Expected boolean, but got ${occluded.constructor.name}`,
);
}
data.occluded = occluded; data.occluded = occluded;
}, },
}, },
@ -190,16 +209,10 @@
* @type {boolean} * @type {boolean}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance * @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/ */
get: () => data.lock, get: () => data.lock,
set: (lock) => { set: (lock) => {
if (typeof (lock) !== 'boolean') { data.updateFlags.lock = true;
throw new window.cvat.exceptions.ArgumentError(
`Expected boolean, but got ${lock.constructor.name}`,
);
}
data.lock = lock; data.lock = lock;
}, },
}, },
@ -210,60 +223,55 @@
* @name attributes * @name attributes
* @type {Object} * @type {Object}
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError} * @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
*/ */
get: () => data.attributes, get: () => data.attributes,
set: (attributes) => { set: (attributes) => {
if (typeof (attributes) !== 'object') { if (typeof (attributes) !== 'object') {
if (typeof (attributes) === 'undefined') {
throw new window.cvat.exceptions.ArgumentError( throw new window.cvat.exceptions.ArgumentError(
`Expected object, but got ${attributes.constructor.name}`, 'Expected attributes are object, but got undefined',
); );
} }
for (let attrId in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, attrId)) {
attrId = +attrId;
if (!Number.isInteger(attrId)) {
throw new window.cvat.exceptions.ArgumentError( throw new window.cvat.exceptions.ArgumentError(
`Expected integer attribute id, but got ${attrId.constructor.name}`, `Expected attributes are object, but got ${attributes.constructor.name}`,
); );
} }
data.attributes[attrId] = attributes[attrId]; for (const attrID of Object.keys(attributes)) {
} data.updateFlags.attributes = true;
data.attributes[attrID] = attributes[attrID];
} }
}, },
}, },
})); }));
this.label = serialized.label; this.label = serialized.label;
this.group = serialized.group; this.group = serialized.group;
this.zOrder = serialized.zOrder; this.zOrder = serialized.zOrder;
this.outside = serialized.outside; this.outside = serialized.outside;
this.keyframe = serialized.keyframe;
this.occluded = serialized.occluded; this.occluded = serialized.occluded;
this.attributes = serialized.attributes; this.attributes = serialized.attributes;
this.lock = false; this.points = serialized.points;
this.color = serialized.color;
this.lock = serialized.lock;
const points = []; data.updateFlags.reset();
for (let i = 0; i < serialized.points.length; i += 2) {
points.push({
x: serialized.points[i],
y: serialized.points[i + 1],
});
}
this.points = points;
} }
/** /**
* Method saves object state in a collection * Method saves/updates an object state in a collection
* @method save * @method save
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @readonly * @readonly
* @instance * @instance
* @async * @async
* @throws {module:API.cvat.exceptions.PluginError} * @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() { async save() {
const result = await PluginRegistry const result = await PluginRegistry
@ -272,21 +280,41 @@
} }
/** /**
* Method deletes object from a collection * Method deletes an object from a collection
* @method delete * @method delete
* @memberof module:API.cvat.classes.ObjectState * @memberof module:API.cvat.classes.ObjectState
* @readonly * @readonly
* @instance * @instance
* @param {boolean} [force=false] delete object even if it is locked
* @async * @async
* @returns {boolean} wheter object was deleted
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async delete() { async delete(force = false) {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper.call(this, ObjectState.prototype.delete); .apiWrapper.call(this, ObjectState.prototype.delete, force);
return result; return result;
} }
} }
// Default implementation saves element in collection
ObjectState.prototype.save.implementation = async function () {
if (this.updateInCollection) {
return this.updateInCollection();
}
return this;
};
// Default implementation do nothing
ObjectState.prototype.delete.implementation = async function (force) {
if (this.deleteFromCollection) {
return this.deleteFromCollection(force);
}
return false;
};
module.exports = ObjectState; module.exports = ObjectState;
})(); })();

@ -179,10 +179,10 @@
} }
async function logout() { async function logout() {
const { backendAPI } = window.cvat.config; const host = window.cvat.config.backendAPI.slice(0, -7);
try { try {
await Axios.get(`${backendAPI}/auth/logout`, { await Axios.get(`${host}/auth/logout`, {
proxy: window.cvat.config.proxy, proxy: window.cvat.config.proxy,
}); });
} catch (errorData) { } catch (errorData) {

Loading…
Cancel
Save