Single-point interpolation (#453)

main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent fa4cf1eb00
commit 58e72a2986

@ -7,9 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Installation guide - Installation guide
- Linear interpolation for a single point
### Changed ### Changed
- - Outside and keyframe buttons in the side panel for all interpolation shapes (they were only for boxes before)
### Deprecated ### Deprecated
- -
@ -20,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fixed incorrect width of shapes borders in some cases - Fixed incorrect width of shapes borders in some cases
- Fixed annotation parser for tracks with a start frame less than the first segment frame - Fixed annotation parser for tracks with a start frame less than the first segment frame
- Fixed interpolation on the server near outside frames
### Security ### Security
- -

@ -1090,12 +1090,19 @@ class TrackManager(ObjectManager):
step = np.subtract(shape1["points"], shape0["points"]) / distance step = np.subtract(shape1["points"], shape0["points"]) / distance
for frame in range(shape0["frame"] + 1, shape1["frame"]): for frame in range(shape0["frame"] + 1, shape1["frame"]):
off = frame - shape0["frame"] off = frame - shape0["frame"]
points = shape0["points"] + step * off if shape1["outside"]:
points = np.asarray(shape0["points"]).reshape(-1, 2)
else:
points = (shape0["points"] + step * off).reshape(-1, 2)
shape = copy.deepcopy(shape0) shape = copy.deepcopy(shape0)
broken_line = geometry.LineString(points.reshape(-1, 2)).simplify(0.05, False) if len(points) == 1:
shape["points"] = points.flatten()
else:
broken_line = geometry.LineString(points).simplify(0.05, False)
shape["points"] = [x for p in broken_line.coords for x in p]
shape["keyframe"] = False shape["keyframe"] = False
shape["frame"] = frame shape["frame"] = frame
shape["points"] = [x for p in broken_line.coords for x in p]
shapes.append(shape) shapes.append(shape)
return shapes return shapes

@ -54,12 +54,12 @@ class ShapeCreatorModel extends Listener {
}); });
} }
if (this._defaultMode === 'interpolation' && this._defaultType === 'box') { // FIXME: In the future we have to make some generic solution
if (this._defaultMode === 'interpolation' && ['box', 'points'].includes(this._defaultType)) {
data.shapes = []; data.shapes = [];
data.shapes.push(Object.assign({}, result, data)); data.shapes.push(Object.assign({}, result, data));
this._shapeCollection.add(data, `interpolation_box`); this._shapeCollection.add(data, `interpolation_${this._defaultType}`);
} } else {
else {
Object.assign(data, result); Object.assign(data, result);
this._shapeCollection.add(data, `annotation_${this._defaultType}`); this._shapeCollection.add(data, `annotation_${this._defaultType}`);
} }
@ -213,11 +213,14 @@ class ShapeCreatorView {
} }
this._typeSelector.on('change', (e) => { this._typeSelector.on('change', (e) => {
let type = $(e.target).prop('value'); // FIXME: In the future we have to make some generic solution
if (type != 'box' && this._modeSelector.prop('value') != 'annotation') { const mode = this._modeSelector.prop('value');
const type = $(e.target).prop('value');
if (type !== 'box' && !(type === 'points' && this._polyShapeSize === 1)
&& mode !== 'annotation') {
this._modeSelector.prop('value', 'annotation'); this._modeSelector.prop('value', 'annotation');
this._controller.setDefaultShapeMode('annotation'); this._controller.setDefaultShapeMode('annotation');
showMessage('Poly shapes available only like annotation shapes'); showMessage('Only the annotation mode allowed for the shape');
} }
this._controller.setDefaultShapeType(type); this._controller.setDefaultShapeType(type);
}).trigger('change'); }).trigger('change');
@ -227,20 +230,30 @@ class ShapeCreatorView {
}).trigger('change'); }).trigger('change');
this._modeSelector.on('change', (e) => { this._modeSelector.on('change', (e) => {
let mode = $(e.target).prop('value'); // FIXME: In the future we have to make some generic solution
if (mode != 'annotation' && this._typeSelector.prop('value') != 'box') { const mode = $(e.target).prop('value');
const type = this._typeSelector.prop('value');
if (mode !== 'annotation' && !(type === 'points' && this._polyShapeSize === 1)
&& type !== 'box') {
this._typeSelector.prop('value', 'box'); this._typeSelector.prop('value', 'box');
this._controller.setDefaultShapeType('box'); this._controller.setDefaultShapeType('box');
showMessage('Only boxes available like interpolation shapes'); showMessage('Only boxes and single point allowed in the interpolation mode');
} }
this._controller.setDefaultShapeMode(mode); this._controller.setDefaultShapeMode(mode);
}).trigger('change'); }).trigger('change');
this._polyShapeSizeInput.on('change', (e) => { this._polyShapeSizeInput.on('change', (e) => {
e.stopPropagation(); e.stopPropagation();
let size = + e.target.value; let size = +e.target.value;
if (size < 0) size = 0; if (size < 0) size = 0;
if (size > 100) size = 0; if (size > 100) size = 0;
const mode = this._modeSelector.prop('value');
const type = this._typeSelector.prop('value');
if (mode === 'interpolation' && type === 'points' && size !== 1) {
showMessage('Only single point allowed in the interpolation mode');
size = 1;
}
e.target.value = size || ''; e.target.value = size || '';
this._polyShapeSize = size; this._polyShapeSize = size;
}).trigger('change'); }).trigger('change');
@ -265,6 +278,7 @@ class ShapeCreatorView {
let size = this._polyShapeSize; let size = this._polyShapeSize;
let sizeDecrement = function() { let sizeDecrement = function() {
if (!--size) { if (!--size) {
numberOfPoints = this._polyShapeSize;
this._drawInstance.draw('done'); this._drawInstance.draw('done');
} }
}.bind(this); }.bind(this);
@ -323,7 +337,7 @@ class ShapeCreatorView {
this._drawInstance.draw('point', e); this._drawInstance.draw('point', e);
lastPoint = { lastPoint = {
x: e.clientX, x: e.clientX,
y: e.clientY y: e.clientY,
}; };
} }
} }

@ -341,8 +341,8 @@ class ShapeModel extends Listener {
} }
switchOutside(frame) { switchOutside(frame) {
// Only for interpolation boxes // Only for interpolation shapes
if (this._type != 'interpolation_box') { if (this._type.split('_')[0] !== 'interpolation') {
return; return;
} }
@ -379,7 +379,7 @@ class ShapeModel extends Listener {
if (frame < this._frame) { if (frame < this._frame) {
if (this._frame in this._attributes.mutable) { if (this._frame in this._attributes.mutable) {
this._attributes.mutable[frame] = this._attributes.mutable[this._frame]; this._attributes.mutable[frame] = this._attributes.mutable[this._frame];
delete(this._attributes.mutable[this._frame]); delete (this._attributes.mutable[this._frame]);
} }
this._frame = frame; this._frame = frame;
} }
@ -388,17 +388,17 @@ class ShapeModel extends Listener {
} }
switchKeyFrame(frame) { switchKeyFrame(frame) {
// Only for interpolation boxes // Only for interpolation shapes
if (this._type != 'interpolation_box') { if (this._type.split('_')[0] !== 'interpolation') {
return; return;
} }
// Undo/redo code // Undo/redo code
let oldPos = Object.assign({}, this._positions[frame]); const oldPos = Object.assign({}, this._positions[frame]);
window.cvat.addAction('Change Keyframe', () => { window.cvat.addAction('Change Keyframe', () => {
this.switchKeyFrame(frame); this.switchKeyFrame(frame);
if (Object.keys(oldPos).length && oldPos.outside) { if (frame in this._positions) {
this.switchOutside(frame); this.updatePosition(frame, oldPos);
} }
}, () => { }, () => {
this.switchKeyFrame(frame); this.switchKeyFrame(frame);
@ -411,19 +411,18 @@ class ShapeModel extends Listener {
this._frame = Object.keys(this._positions).map((el) => +el).sort((a,b) => a - b)[1]; this._frame = Object.keys(this._positions).map((el) => +el).sort((a,b) => a - b)[1];
if (frame in this._attributes.mutable) { if (frame in this._attributes.mutable) {
this._attributes.mutable[this._frame] = this._attributes.mutable[frame]; this._attributes.mutable[this._frame] = this._attributes.mutable[frame];
delete(this._attributes.mutable[frame]); delete (this._attributes.mutable[frame]);
} }
} }
delete(this._positions[frame]); delete (this._positions[frame]);
} } else {
else {
let position = this._interpolatePosition(frame); let position = this._interpolatePosition(frame);
this.updatePosition(frame, position, true); this.updatePosition(frame, position, true);
if (frame < this._frame) { if (frame < this._frame) {
if (this._frame in this._attributes.mutable) { if (this._frame in this._attributes.mutable) {
this._attributes.mutable[frame] = this._attributes.mutable[this._frame]; this._attributes.mutable[frame] = this._attributes.mutable[this._frame];
delete(this._attributes.mutable[this._frame]); delete (this._attributes.mutable[this._frame]);
} }
this._frame = frame; this._frame = frame;
} }
@ -917,7 +916,7 @@ class PolyShapeModel extends ShapeModel {
} }
return Object.assign({}, leftPos, { return Object.assign({}, leftPos, {
outside: leftFrame != frame, outside: leftPos.outside || leftFrame !== frame,
}); });
} }
@ -1145,6 +1144,60 @@ class PointsModel extends PolyShapeModel {
this._minPoints = 1; this._minPoints = 1;
} }
_interpolatePosition(frame) {
if (this._type.startsWith('annotation')) {
return Object.assign({}, this._positions[this._frame], {
outside: this._frame !== frame,
});
}
let [leftFrame, rightFrame] = this._neighboringFrames(frame);
if (frame in this._positions) {
leftFrame = frame;
}
let leftPos = null;
let rightPos = null;
if (leftFrame != null) leftPos = this._positions[leftFrame];
if (rightFrame != null) rightPos = this._positions[rightFrame];
if (!leftPos) {
if (rightPos) {
return Object.assign({}, rightPos, {
outside: true,
});
}
return {
outside: true,
};
}
if (frame === leftFrame || leftPos.outside || !rightPos || rightPos.outside) {
return Object.assign({}, leftPos);
}
const rightPoints = PolyShapeModel.convertStringToNumberArray(rightPos.points);
const leftPoints = PolyShapeModel.convertStringToNumberArray(leftPos.points);
if (rightPoints.length === leftPoints.length && leftPoints.length === 1) {
const moveCoeff = (frame - leftFrame) / (rightFrame - leftFrame);
const interpolatedPoints = [{
x: leftPoints[0].x + (rightPoints[0].x - leftPoints[0].x) * moveCoeff,
y: leftPoints[0].y + (rightPoints[0].y - leftPoints[0].y) * moveCoeff,
}];
return Object.assign({}, leftPos, {
points: PolyShapeModel.convertNumberArrayToString(interpolatedPoints),
});
}
return Object.assign({}, leftPos, {
outside: true,
});
}
distance(mousePos, frame) { distance(mousePos, frame) {
let pos = this._interpolatePosition(frame); let pos = this._interpolatePosition(frame);
if (pos.outside) return Number.MAX_SAFE_INTEGER; if (pos.outside) return Number.MAX_SAFE_INTEGER;
@ -1958,19 +2011,17 @@ class ShapeView extends Listener {
if (type.split('_')[0] == 'interpolation') { if (type.split('_')[0] == 'interpolation') {
let interpolationCenter = document.createElement('center'); let interpolationCenter = document.createElement('center');
if (type.split('_')[1] == 'box') { let outsideButton = document.createElement('button');
let outsideButton = document.createElement('button'); outsideButton.classList.add('graphicButton', 'outsideButton');
outsideButton.classList.add('graphicButton', 'outsideButton');
let keyframeButton = document.createElement('button'); let keyframeButton = document.createElement('button');
keyframeButton.classList.add('graphicButton', 'keyFrameButton'); keyframeButton.classList.add('graphicButton', 'keyFrameButton');
interpolationCenter.appendChild(outsideButton); interpolationCenter.appendChild(outsideButton);
interpolationCenter.appendChild(keyframeButton); interpolationCenter.appendChild(keyframeButton);
this._uis.buttons['outside'] = outsideButton; this._uis.buttons['outside'] = outsideButton;
this._uis.buttons['keyframe'] = keyframeButton; this._uis.buttons['keyframe'] = keyframeButton;
}
let prevKeyFrameButton = document.createElement('button'); let prevKeyFrameButton = document.createElement('button');
prevKeyFrameButton.classList.add('graphicButton', 'prevKeyFrameButton'); prevKeyFrameButton.classList.add('graphicButton', 'prevKeyFrameButton');
@ -3125,7 +3176,7 @@ class PointsView extends PolyShapeView {
_drawPointMarkers(position) { _drawPointMarkers(position) {
if (this._uis.points) { if (this._uis.points || position.outside) {
return; return;
} }

Loading…
Cancel
Save