Release 0.2.0 (#64)

* Move function from closure to class method
* Bug has been fixed: Failed to execute 'inverse' on 'SVGMatrix': The matrix is not invertible
* Performance of frame changing has been increased due to UI containers had detached from DOM before their items were created
* Bug has been fixed: Defiant doesn't support dash (-) in xpath nodes (#53)
* Data validation on client and server (#51)
* Migration files have been added (#59)
* Big int migration file has been rewritten with Django syntax (#60)
* Some memory leaks have been fixed (#61)
main
Boris Sekachev 8 years ago committed by Nikita Manovich
parent a50b4cc144
commit eac18ff911

@ -761,14 +761,29 @@ class _AnnotationForJob(_Annotation):
label = _Label(self.db_labels[int(path['label_id'])])
boxes = []
frame = -1
has_boxes_on_prev_segm = False
last_box_on_prev_segm = None
has_box_on_start_frame = False
for box in path['shapes']:
if int(box['frame']) < self.start_frame:
has_boxes_on_prev_segm = True
if last_box_on_prev_segm is None or int(last_box_on_prev_segm["frame"]) < int(box["frame"]):
last_box_on_prev_segm = box
elif int(box['frame']) == self.start_frame:
has_box_on_start_frame = True
break
if has_boxes_on_prev_segm and not has_box_on_start_frame:
last_box_on_prev_segm["frame"] = self.start_frame
for box in path['shapes']:
if int(box['frame']) <= self.stop_frame:
if int(box['frame']) <= self.stop_frame and int(box['frame']) >= self.start_frame:
frame_idx = int(box['frame']) if db_task.mode == 'annotation' else 0
xtl, ytl, xbr, ybr = self._clamp_box(float(box['xtl']), float(box['ytl']),
float(box['xbr']), float(box['ybr']), image_meta['original_size'][frame_idx])
tracked_box = _TrackedBox(xtl, ytl, xbr, ybr, int(box['frame']), strtobool(str(box['occluded'])),
int(box['z_order']), strtobool(str(box['outside'])))
assert tracked_box.frame > frame
assert tracked_box.frame > frame
frame = tracked_box.frame
for attr in box['attributes']:
@ -780,7 +795,7 @@ class _AnnotationForJob(_Annotation):
boxes.append(tracked_box)
else:
self.logger.error("init_from_client: ignore frame #%d " +
"because stop_frame is %d", int(box['frame']), self.stop_frame)
"because it out of segment range [%d-%d]", int(box['frame']), self.start_frame, self.stop_frame)
attributes = []
for attr in path['attributes']:
@ -790,7 +805,7 @@ class _AnnotationForJob(_Annotation):
attributes.append(attr)
assert frame <= self.stop_frame
box_path = _BoxPath(label, int(path['frame']), self.stop_frame,
box_path = _BoxPath(label, min(list(map(lambda box: box.frame, boxes))), self.stop_frame,
int(path['group_id']), boxes, attributes)
self.box_paths.append(box_path)
@ -799,8 +814,23 @@ class _AnnotationForJob(_Annotation):
label = _Label(self.db_labels[int(path['label_id'])])
poly_shapes = []
frame = -1
has_shapes_on_prev_segm = False
last_shape_on_prev_segm = None
has_shape_on_start_frame = False
for poly_shape in path['shapes']:
if int(poly_shape['frame']) < self.start_frame:
has_shapes_on_prev_segm = True
if last_shape_on_prev_segm is None or int(last_shape_on_prev_segm["frame"]) < (poly_shape["frame"]):
last_shape_on_prev_segm = box
elif int(poly_shape['frame']) == self.start_frame:
has_shape_on_start_frame = True
break
if has_shapes_on_prev_segm and not has_shape_on_start_frame:
last_shape_on_prev_segm["frame"] = self.start_frame
for poly_shape in path['shapes']:
if int(poly_shape['frame']) <= self.stop_frame:
if int(poly_shape['frame']) <= self.stop_frame and int(poly_shape['frame']) >= self.start_frame:
frame_idx = int(poly_shape['frame']) if db_task.mode == 'annotation' else 0
points = self._clamp_poly(poly_shape['points'], image_meta['original_size'][frame_idx])
tracked_poly_shape = _TrackedPolyShape(points, int(poly_shape['frame']), strtobool(str(poly_shape['occluded'])),
@ -817,7 +847,7 @@ class _AnnotationForJob(_Annotation):
poly_shapes.append(tracked_poly_shape)
else:
self.logger.error("init_from_client: ignore frame #%d " +
"because stop_frame is %d", int(poly_shape['frame']), self.stop_frame)
"because it out of segment range [%d-%d]", int(poly_shape['frame']), self.start_frame, self.stop_frame)
attributes = []
for attr in path['attributes']:
@ -826,7 +856,7 @@ class _AnnotationForJob(_Annotation):
attr = _Attribute(spec, str(attr['value']))
attributes.append(attr)
poly_path = _PolyPath(label, int(path['frame']), self.stop_frame + 1,
poly_path = _PolyPath(label, min(list(map(lambda shape: shape.frame, poly_shapes))), self.stop_frame + 1,
int(path['group_id']), poly_shapes, attributes)
getattr(self, poly_path_type).append(poly_path)

@ -0,0 +1,20 @@
# Generated by Django 2.0.3 on 2018-09-17 11:24
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('engine', '0007_task_flipped'),
]
operations = [
migrations.AlterField(
model_name='task',
name='owner',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
]

@ -0,0 +1,170 @@
# Generated by Django 2.0.3 on 2018-09-17 11:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0008_auto_20180917_1424'),
]
operations = [
migrations.AlterField(
model_name='labeledbox',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='labeledboxattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='labeledpoints',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='labeledpointsattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='labeledpolygon',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='labeledpolygonattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='labeledpolyline',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='labeledpolylineattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='objectpath',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='objectpathattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedbox',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedboxattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedpoints',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedpointsattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedpolygon',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedpolygonattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedpolyline',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='trackedpolylineattributeval',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='objectpathattributeval',
name='track_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='objectpathattributeval',
name='track_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='trackedpoints',
name='track_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='trackedpolygon',
name='track_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='trackedpolyline',
name='track_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='trackedboxattributeval',
name='box_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='trackedpointsattributeval',
name='points_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='trackedpolygonattributeval',
name='polygon_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='trackedpolylineattributeval',
name='polyline_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='labeledboxattributeval',
name='box_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='labeledpointsattributeval',
name='points_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='labeledpolygonattributeval',
name='polygon_id',
field=models.BigIntegerField(),
),
migrations.AlterField(
model_name='labeledpolylineattributeval',
name='polyline_id',
field=models.BigIntegerField(),
),
]

@ -128,6 +128,7 @@ class AttributeSpec(models.Model):
class AttributeVal(models.Model):
# TODO: add a validator here to be sure that it corresponds to self.label
id = models.BigAutoField(primary_key=True)
spec = models.ForeignKey(AttributeSpec, on_delete=models.CASCADE)
value = models.CharField(max_length=64)
class Meta:
@ -148,6 +149,7 @@ class Shape(models.Model):
abstract = True
class BoundingBox(Shape):
id = models.BigAutoField(primary_key=True)
xtl = models.FloatField()
ytl = models.FloatField()
xbr = models.FloatField()
@ -156,6 +158,7 @@ class BoundingBox(Shape):
abstract = True
class PolyShape(Shape):
id = models.BigAutoField(primary_key=True)
points = models.TextField()
class Meta:
abstract = True
@ -185,6 +188,7 @@ class LabeledPointsAttributeVal(AttributeVal):
points = models.ForeignKey(LabeledPoints, on_delete=models.CASCADE)
class ObjectPath(Annotation):
id = models.BigAutoField(primary_key=True)
shapes = models.CharField(max_length=10, default='boxes')
class ObjectPathAttributeVal(AttributeVal):

@ -98,8 +98,9 @@ html {
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
padding: 3px 0px;
transition: 0.3s;
}
.tab button:hover {

@ -1,29 +1,29 @@
From b89380c65ea8bc9231cc98a6ae0e812227c85b3d Mon Sep 17 00:00:00 2001
From 5eeb1092c64865c555671ed585da18f974c9c10c Mon Sep 17 00:00:00 2001
From: Boris Sekachev <boris.sekachev@intel.com>
Date: Tue, 10 Jul 2018 14:31:13 +0300
Date: Tue, 18 Sep 2018 15:58:20 +0300
Subject: [PATCH] tmp
---
.../engine/static/engine/js/3rdparty/svg.draggable.js | 1 +
cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js | 17 +++++++++++++++--
.../apps/engine/static/engine/js/3rdparty/svg.resize.js | 5 +++--
.../apps/engine/static/engine/js/3rdparty/svg.select.js | 1 +
4 files changed, 20 insertions(+), 4 deletions(-)
.../apps/engine/static/engine/js/3rdparty/svg.select.js | 5 ++++-
4 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
index d88abf5..06158f1 100644
index d88abf5..aba474c 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
@@ -109,6 +109,7 @@
// while dragging
DragHandler.prototype.drag = function(e){
+ this.m = this.el.node.getScreenCTM().inverse();
var box = this.getBBox()
, p = this.transformPoint(e)
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
index 68dbf2a..9884b75 100644
index 68dbf2a..20a6917 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
@@ -18,6 +18,7 @@
@ -31,13 +31,13 @@ index 68dbf2a..9884b75 100644
this.lastUpdateCall = null;
this.options = {};
+ this.set = new SVG.Set();
// Merge options and defaults
for (var i in this.el.draw.defaults) {
@@ -139,6 +140,8 @@
// Call the calc-function which calculates the new position and size
this.calc(event);
+ this.m = this.el.node.getScreenCTM().inverse();
+ this.offset = { x: window.pageXOffset, y: window.pageYOffset };
// Fire the `drawupdate`-event
@ -46,7 +46,7 @@ index 68dbf2a..9884b75 100644
@@ -160,6 +163,16 @@
this.el.fire('drawcancel');
};
+ // Undo last drawed point
+ PaintHandler.prototype.undo = function () {
+ if (this.set.length()) {
@ -59,24 +59,24 @@ index 68dbf2a..9884b75 100644
+
// Calculate the corrected position when using `snapToGrid`
PaintHandler.prototype.snapToGrid = function (draw) {
@@ -371,14 +384,14 @@
this.set.clear();
- for (var i = 0; i < array.length; ++i) {
+ for (var i = 0; i < array.length - 1; ++i) {
this.p.x = array[i][0]
this.p.y = array[i][1]
var p = this.p.matrixTransform(this.parent.node.getScreenCTM().inverse().multiply(this.el.node.getScreenCTM()));
- this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y));
+ this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y)).addClass("svg_draw_point");
}
}
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
index 0c3b63d..fb5dc26 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
@ -91,27 +91,53 @@ index 0c3b63d..fb5dc26 100644
+ y: event.clientY != null ? event.clientY : event.touches[0].clientY
};
};
@@ -343,6 +343,7 @@
}
return;
}
+ this.m = this.el.node.getScreenCTM().inverse();
// Calculate the difference between the mouseposition at start and now
var txPt = this._extractPosition(event);
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
index 47e07bd..f1d0c02 100644
index 47e07bd..cee6d34 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
@@ -160,6 +160,7 @@ SelectHandler.prototype.drawPoints = function () {
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
ev.stopPropagation();
+ if (ev.which != 1) return false;
var x = ev.pageX || ev.touches[0].pageX;
var y = ev.pageY || ev.touches[0].pageY;
_this.el.fire('point', {x: x, y: y, i: k, event: ev});
--
@@ -361,6 +362,7 @@ SelectHandler.prototype.cleanup = function () {
// stop watching the element, remove the selection
this.rectSelection.set.each(function () {
this.remove();
+ SVG.off(this.node);
});
this.rectSelection.set.clear();
@@ -371,6 +373,7 @@ SelectHandler.prototype.cleanup = function () {
// Remove all points, clear the set, stop watching the element
this.pointSelection.set.each(function () {
this.remove();
+ SVG.off(this.node);
});
this.pointSelection.set.clear();
@@ -379,8 +382,8 @@ SelectHandler.prototype.cleanup = function () {
if (!this.pointSelection.isSelected && !this.rectSelection.isSelected) {
this.nested.remove();
+ SVG.off(this.node);
delete this.nested;
-
}
};
--
2.7.4

@ -322,8 +322,6 @@ class AnnotationParser {
Ignore all frames more then stop.
*/
let significant = keyFrame || frame === this._startFrame;
significant = significant && frame >= this._startFrame;
significant = significant && frame <= this._stopFrame;
if (significant) {
let attributeList = this._getAttributeList(shape, labelId);
@ -348,7 +346,7 @@ class AnnotationParser {
path.attributes = pathAttributes;
if (type === 'boxes') {
let [xtl, ytl, xbr, ybr, occluded, z_order] = this._getBoxPosition(shape, frame);
let [xtl, ytl, xbr, ybr, occluded, z_order] = this._getBoxPosition(shape, Math.clamp(frame, this._startFrame, this._stopFrame));
path.shapes.push({
frame: frame,
occluded: occluded,
@ -362,7 +360,7 @@ class AnnotationParser {
});
}
else {
let [points, occluded, z_order] = this._getPolyPosition(shape, frame);
let [points, occluded, z_order] = this._getPolyPosition(shape, Math.clamp(frame, this._startFrame, this._stopFrame));
path.shapes.push({
frame: frame,
occluded: occluded,

@ -12,8 +12,10 @@ function callAnnotationUI(jid) {
let loadJobEvent = Logger.addContinuedEvent(Logger.EventType.loadJob);
serverRequest("/get/job/" + jid, function(job) {
serverRequest("get/annotation/job/" + jid, function(data) {
buildAnnotationUI(job, data, loadJobEvent);
$('#loadingOverlay').remove();
setTimeout(() => {
buildAnnotationUI(job, data, loadJobEvent);
}, 0);
});
});
}

@ -44,6 +44,8 @@ class FrameProvider extends Listener {
this._loaded = frame;
this._frameCollection[frame] = image;
this._loadAllowed = true;
image.onload = null;
image.onerror = null;
this.notify();
}
@ -109,6 +111,8 @@ class FrameProvider extends Listener {
image.onload = this._onImageLoad.bind(this, image, frame);
image.onerror = () => {
this._loadAllowed = true;
image.onload = null;
image.onerror = null;
};
image.src = `get/task/${this._tid}/frame/${frame}`;
}.bind(this), 25);

@ -149,7 +149,7 @@ class ShapeBufferModel extends Listener {
this._collection.add(object, `annotation_${this._shape.type}`);
}
// Undo/redo code
// Undo/redo code
let model = this._collection.shapes.slice(-1)[0];
window.cvat.addAction('Paste Object', () => {
model.removed = true;

@ -1247,8 +1247,17 @@ class ShapeCollectionView {
view.erase();
}
this._currentViews = [];
// Save parents and detach elements from DOM
// in order to increase performance in the buildShapeView function
let parents = {
uis: this._UIContent.parent(),
shapes: this._frameContent.node.parentNode
};
this._frameContent.node.parent = null;
this._UIContent.detach();
this._currentViews = [];
for (let shape of collection.currentShapes) {
let model = shape.model;
let view = buildShapeView(model, buildShapeController(model), this._frameContent, this._UIContent);
@ -1259,6 +1268,10 @@ class ShapeCollectionView {
view.subscribe(this);
this._labelsContent.find(`.labelContentElement[label_id="${model.label}"]`).removeClass('hidden');
}
parents.shapes.append(this._frameContent.node);
parents.uis.prepend(this._UIContent);
ShapeCollectionView.sortByZOrder();
}

@ -261,6 +261,75 @@ class ShapeCreatorView {
}.bind(this));
}
_createPolyEvents() {
// If number of points for poly shape specified, use it.
// Dicrement number on draw new point events. Drawstart trigger when create first point
if (this._polyShapeSize) {
let size = this._polyShapeSize;
let sizeDecrement = function() {
if (!--size) {
this._drawInstance.draw('done');
}
}.bind(this);
let sizeIncrement = function() {
size ++;
};
this._drawInstance.on('drawstart', sizeDecrement);
this._drawInstance.on('drawpoint', sizeDecrement);
this._drawInstance.on('undopoint', sizeIncrement);
}
// Otherwise draw will stop by Ctrl + N press
// Callbacks for point scale
this._drawInstance.on('drawstart', this._rescaleDrawPoints.bind(this));
this._drawInstance.on('drawpoint', this._rescaleDrawPoints.bind(this));
this._frameContent.on('mousedown.shapeCreator', (e) => {
if (e.which === 3) {
this._drawInstance.draw('undo');
}
});
this._drawInstance.on('drawstop', () => {
this._frameContent.off('mousedown.shapeCreator');
});
// Also we need callback on drawdone event for get points
this._drawInstance.on('drawdone', function(e) {
let points = PolyShapeModel.convertStringToNumberArray(e.target.getAttribute('points'));
for (let point of points) {
point.x = Math.clamp(point.x, 0, window.cvat.player.geometry.frameWidth);
point.y = Math.clamp(point.y, 0, window.cvat.player.geometry.frameHeight);
}
// Min 2 points for polyline and 3 points for polygon
if (points.length) {
if (this._type === 'polyline' && points.length < 2) {
showMessage("Min 2 points must be for polyline drawing.");
}
else if (this._type === 'polygon' && points.length < 3) {
showMessage("Min 3 points must be for polygon drawing.");
}
else {
points = PolyShapeModel.convertNumberArrayToString(points);
// Update points in view in order to get updated box
e.target.setAttribute('points', points);
let box = e.target.getBBox();
if (box.width * box.height >= AREA_TRESHOLD || this._type === 'points' ||
this._type === 'polyline' && (box.width >= AREA_TRESHOLD || box.height >= AREA_TRESHOLD)) {
this._controller.finish({points: e.target.getAttribute('points')}, this._type);
}
}
}
this._controller.switchCreateMode(true);
}.bind(this));
}
_create() {
let sizeUI = null;
switch(this._type) {
@ -302,7 +371,7 @@ class ShapeCreatorView {
this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({
'stroke-width': 0,
});
createPolyEvents.call(this);
this._createPolyEvents();
break;
case 'polygon':
if (this._polyShapeSize && this._polyShapeSize < 3) {
@ -315,7 +384,7 @@ class ShapeCreatorView {
this._drawInstance = this._frameContent.polygon().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / this._scale,
});
createPolyEvents.call(this);
this._createPolyEvents();
break;
case 'polyline':
if (this._polyShapeSize && this._polyShapeSize < 2) {
@ -328,7 +397,7 @@ class ShapeCreatorView {
this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / this._scale,
});
createPolyEvents.call(this);
this._createPolyEvents();
break;
default:
throw Error(`Bad type found ${this._type}`);
@ -368,73 +437,6 @@ class ShapeCreatorView {
this._drawInstance.attr({
'z_order': Number.MAX_SAFE_INTEGER,
});
function createPolyEvents() {
// If number of points for poly shape specified, use it.
// Dicrement number on draw new point events. Drawstart trigger when create first point
if (this._polyShapeSize) {
let size = this._polyShapeSize;
let sizeDecrement = function() {
if (!--size) {
this._drawInstance.draw('done');
}
}.bind(this);
let sizeIncrement = function() {
size ++;
};
this._drawInstance.on('drawstart', sizeDecrement);
this._drawInstance.on('drawpoint', sizeDecrement);
this._drawInstance.on('undopoint', sizeIncrement);
}
// Otherwise draw will stop by Ctrl + N press
// Callbacks for point scale
this._drawInstance.on('drawstart', this._rescaleDrawPoints.bind(this));
this._drawInstance.on('drawpoint', this._rescaleDrawPoints.bind(this));
this._frameContent.on('mousedown.shapeCreator', (e) => {
if (e.which === 3) {
this._drawInstance.draw('undo');
}
});
this._drawInstance.on('drawstop', () => {
this._frameContent.off('mousedown.shapeCreator');
});
// Also we need callback on drawdone event for get points
this._drawInstance.on('drawdone', function(e) {
let points = PolyShapeModel.convertStringToNumberArray(e.target.getAttribute('points'));
for (let point of points) {
point.x = Math.clamp(point.x, 0, window.cvat.player.geometry.frameWidth);
point.y = Math.clamp(point.y, 0, window.cvat.player.geometry.frameHeight);
}
// Min 2 points for polyline and 3 points for polygon
if (points.length) {
if (this._type === 'polyline' && points.length < 2) {
showMessage("Min 2 points must be for polyline drawing.");
}
else if (this._type === 'polygon' && points.length < 3) {
showMessage("Min 3 points must be for polygon drawing.");
}
else {
points = PolyShapeModel.convertNumberArrayToString(points);
// Update points in view in order to get updated box
e.target.setAttribute('points', points);
let box = e.target.getBBox();
if (box.width * box.height >= AREA_TRESHOLD || this._type === 'points' ||
this._type === 'polyline' && (box.width >= AREA_TRESHOLD || box.height >= AREA_TRESHOLD)) {
this._controller.finish({points: e.target.getAttribute('points')}, this._type);
}
}
}
this._controller.switchCreateMode(true);
}.bind(this));
}
}
_rescaleDrawPoints() {
@ -477,7 +479,7 @@ class ShapeCreatorView {
this._type = model.defaultType;
this._mode = model.defaultMode;
if (this._type === 'box') {
if (!['polygon', 'polyline', 'points'].includes(this._type)) {
this._drawAim();
}
@ -486,10 +488,7 @@ class ShapeCreatorView {
this._create();
}
else {
if (this._type === 'box') {
this._removeAim();
}
this._removeAim();
this._cancel = true;
this._createButton.text("Create Shape");
document.oncontextmenu = null;
@ -526,7 +525,7 @@ class ShapeCreatorView {
this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale);
this._aim.y.attr('stroke-width', STROKE_WIDTH / this._scale);
}
if (this._type != 'points') {
if (['box', 'polygon', 'polyline'].includes(this._type)) {
this._drawInstance.attr('stroke-width', STROKE_WIDTH / this._scale);
}
}

@ -26,10 +26,11 @@ class FilterModel {
lock: shape.model.lock
};
// We replace all dashes due to defiant.js can't work with it
function convertAttributes(attributes) {
let converted = {};
for (let attrId in attributes) {
converted[attributes[attrId].name.toLowerCase()] = ('' + attributes[attrId].value).toLowerCase();
converted[attributes[attrId].name.toLowerCase().replace(/-/g, "_")] = ('' + attributes[attrId].value).toLowerCase();
}
return converted;
}
@ -38,11 +39,11 @@ class FilterModel {
_convertCollection(collection) {
let converted = {};
for (let labelId in this._labels) {
converted[this._labels[labelId]] = [];
converted[this._labels[labelId].replace(/-/g, "_")] = [];
}
for (let shape of collection) {
converted[this._labels[shape.model.label].toLowerCase()].push(this._convertShape(shape));
converted[this._labels[shape.model.label].toLowerCase().replace(/-/g, "_")].push(this._convertShape(shape));
}
return converted;
}
@ -109,7 +110,7 @@ class FilterView {
this._filterString.on('change', (e) => {
let value = $.trim(e.target.value);
if (value.length) {
value = value.split('|').map(x => '/d:data/' + x).join('|').toLowerCase();
value = value.split('|').map(x => '/d:data/' + x).join('|').toLowerCase().replace(/-/g, "_");
}
if (this._controller.updateFilter(value)) {
this._filterString.css('color', 'green');

@ -22,7 +22,9 @@ class ShapeModel extends Listener {
this._type = type;
this._color = color;
this._label = data.label_id;
this._frame = data.frame;
this._frame = type.split('_')[0] === 'annotation' ? data.frame :
positions.filter((pos) => pos.frame < window.cvat.player.frames.start).length ?
window.cvat.player.frames.start : Math.min(...positions.map((pos) => pos.frame));
this._removed = false;
this._locked = false;
this._merging = false;
@ -333,7 +335,17 @@ class ShapeModel extends Listener {
let oldPos = Object.assign({}, this._positions[frame]);
window.cvat.addAction('Change Outside', () => {
if (!Object.keys(oldPos).length) {
// Frame hasn't been a keyframe, remove it from position and redestribute attributes
delete this._positions[frame];
this._frame = Math.min(...Object.keys(this._positions).map((el) => +el));
if (frame < this._frame && frame in this._attributes.mutable) {
this._attributes.mutable[this._frame] = this._attributes.mutable[frame];
}
if (frame in this._attributes.mutable) {
delete this._attributes.mutable[frame];
}
this._updateReason = 'outside';
this.notify();
}
@ -349,6 +361,15 @@ class ShapeModel extends Listener {
position.outside = !position.outside;
this.updatePosition(frame, position, true);
// Update the start frame if need and redestribute attributes
if (frame < this._frame) {
if (this._frame in this._attributes.mutable) {
this._attributes.mutable[frame] = this._attributes.mutable[this._frame];
delete(this._attributes.mutable[this._frame]);
}
this._frame = frame;
}
this._updateReason = 'outside';
this.notify();
}
@ -360,38 +381,28 @@ class ShapeModel extends Listener {
}
// Undo/redo code
let oldPos = Object.assign({}, this._positions[frame]);
window.cvat.addAction('Change Keyframe', () => {
if (!Object.keys(oldPos).length) {
delete this._positions[frame];
this._updateReason = 'outside';
this.notify();
}
else {
this.updatePosition(frame, oldPos, true);
this._updateReason = 'keyframe';
this.notify();
}
this.switchKeyFrame(frame);
}, () => {
this.switchKeyFrame(frame);
}, frame);
// End of undo/redo code
if (frame in this._positions && Object.keys(this._positions).length > 1) {
// If frame is first frame, need to redestribute attributes to new first frame
// If frame is first object frame, need redestribute attributes
if (frame === this._frame) {
this._frame = +Object.keys(this._positions).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) {
this._attributes.mutable[this._frame] = this._attributes.mutable[frame];
delete(this._attributes.mutable[frame]);
}
}
delete(this._positions[frame]);
if (frame in this._attributes.mutable) {
delete(this._attributes.mutable[frame]);
}
}
else {
let position = this._interpolatePosition(frame);
this.updatePosition(frame, position, true);
if (frame < this._frame) {
if (this._frame in this._attributes.mutable) {
this._attributes.mutable[frame] = this._attributes.mutable[this._frame];
@ -399,9 +410,6 @@ class ShapeModel extends Listener {
}
this._frame = frame;
}
let position = this._interpolatePosition(frame);
this.updatePosition(frame, position, true);
}
this._updateReason = 'keyframe';
this.notify();
@ -803,17 +811,44 @@ class BoxModel extends ShapeModel {
static importPositions(positions) {
let imported = {};
if (this._type === 'interpolation_box') {
let last_key_in_prev_segm = null;
let segm_start = window.cvat.player.frames.start;
let segm_stop = window.cvat.player.frames.stop;
for (let pos of positions) {
imported[pos.frame] = {
xtl: pos.xtl,
ytl: pos.ytl,
xbr: pos.xbr,
ybr: pos.ybr,
occluded: pos.occluded,
outside: pos.outside,
z_order: pos.z_order,
let frame = pos.frame;
if (frame >= segm_start && frame <= segm_stop) {
imported[frame] = {
xtl: pos.xtl,
ytl: pos.ytl,
xbr: pos.xbr,
ybr: pos.ybr,
occluded: pos.occluded,
outside: pos.outside,
z_order: pos.z_order,
};
}
else {
console.log(`Frame ${frame} has been found in segment [${segm_start}-${segm_stop}]. It have been ignored.`);
if (!last_key_in_prev_segm || frame > last_key_in_prev_segm.frame) {
last_key_in_prev_segm = pos;
}
}
}
if (last_key_in_prev_segm && !(segm_start in imported)) {
imported[segm_start] = {
xtl: last_key_in_prev_segm.xtl,
ytl: last_key_in_prev_segm.ytl,
xbr: last_key_in_prev_segm.xbr,
ybr: last_key_in_prev_segm.ybr,
occluded: last_key_in_prev_segm.occluded,
outside: last_key_in_prev_segm.outside,
z_order: last_key_in_prev_segm.z_order,
};
}
return imported;
}
@ -837,6 +872,7 @@ class PolyShapeModel extends ShapeModel {
this._setupKeyFrames();
}
_interpolatePosition(frame) {
if (frame in this._positions) {
return Object.assign({}, this._positions[frame], {
@ -1036,14 +1072,37 @@ class PolyShapeModel extends ShapeModel {
static importPositions(positions) {
let imported = {};
if (this._type.startsWith('interpolation')) {
let last_key_in_prev_segm = null;
let segm_start = window.cvat.player.frames.start;
let segm_stop = window.cvat.player.frames.stop;
for (let pos of positions) {
imported[pos.frame] = {
points: pos.points,
occluded: pos.occluded,
outside: pos.outside,
z_order: pos.z_order,
let frame = pos.frame;
if (frame >= segm_start && frame <= segm_stop) {
imported[pos.frame] = {
points: pos.points,
occluded: pos.occluded,
outside: pos.outside,
z_order: pos.z_order,
};
}
else {
console.log(`Frame ${frame} has been found in segment [${segm_start}-${segm_stop}]. It have been ignored.`);
if (!last_key_in_prev_segm || frame > last_key_in_prev_segm.frame) {
last_key_in_prev_segm = pos;
}
}
}
if (last_key_in_prev_segm && !(segm_start in imported)) {
imported[segm_start] = {
points: last_key_in_prev_segm.points,
occluded: last_key_in_prev_segm.occluded,
outside: last_key_in_prev_segm.outside,
z_order: last_key_in_prev_segm.z_order,
};
}
return imported;
}
@ -1587,8 +1646,8 @@ class ShapeView extends Listener {
_removeShapeUI() {
if (this._uis.shape) {
this._uis.shape.off('click');
this._uis.shape.remove();
SVG.off(this._uis.shape.node);
this._uis.shape = null;
}
}
@ -1597,6 +1656,7 @@ class ShapeView extends Listener {
_removeShapeText() {
if (this._uis.text) {
this._uis.text.remove();
SVG.off(this._uis.text.node);
this._uis.text = null;
}
}

@ -356,6 +356,9 @@
.customizedTab {
border-radius: 5px 5px 0px 0px;
width: 100%;
width: 15%;
float: left;
margin: 0px 10px;
}
/* ----------------------- IDs ----------------------- */
@ -392,7 +395,9 @@
}
#uiContent, #trackManagement, #aamMenu, #labelsContent {
border: 1px black solid;
border-bottom: 1px solid black;
border-right: 1px solid black;
border-left: 1px solid black;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0,0,0,0.5);
background-color: #B0C4DE;

@ -362,7 +362,7 @@ def _parse_labels(labels):
for token in shlex.split(labels):
if token[0] != "~" and token[0] != "@":
if token in parsed_labels:
raise ValueError("labels string is not corect. " +
raise ValueError("labels string is not corect. " +
"`{}` label is specified at least twice.".format(token))
parsed_labels[token] = {}
@ -388,7 +388,7 @@ def _parse_labels(labels):
"`{}` attribute has incorrect format.".format(attr['name']))
if attr['name'] in parsed_labels[last_label]:
raise ValueError("labels string is not corect. " +
raise ValueError("labels string is not corect. " +
"`{}` attribute is specified at least twice.".format(attr['name']))
parsed_labels[last_label][attr['name']] = attr
@ -544,7 +544,6 @@ def _find_and_compress_images(upload_dir, output_dir, db_task, compress_quality,
filenames.sort()
if len(filenames):
compressed_names = []
for idx, name in enumerate(filenames):
job.meta['status'] = 'Images are being compressed.. {}%'.format(idx * 100 // len(filenames))
job.save_meta()
@ -554,10 +553,12 @@ def _find_and_compress_images(upload_dir, output_dir, db_task, compress_quality,
image = image.transpose(Image.ROTATE_180)
image.save(compressed_name, quality=compress_quality, optimize=True)
image.close()
compressed_names.append(compressed_name)
if compressed_name != name:
os.remove(name)
filenames = compressed_names
# PIL::save uses filename in order to define image extension.
# We need save it as jpeg for compression and after rename the file
# Else annotation file will contain invalid file names (with other extensions)
os.rename(compressed_name, name)
for frame, image_orig_path in enumerate(filenames):
image_dest_path = _get_frame_path(frame, output_dir)

@ -375,11 +375,12 @@
</div>
</div>
<div class="tab customizedTab">
<button class="h2 regular activeTabButton" id="sidePanelObjectsButton" style="width: 50%">Objects</button>
<button class="h2 regular" id="sidePanelLabelsButton" style="width: 50%">Labels</button>
</div>
<div id="taskAnnotationRightPanel">
<div class="tab customizedTab">
<button class="h2 regular activeTabButton" id="sidePanelObjectsButton" style="width: 50%">Objects</button>
<button class="h2 regular" id="sidePanelLabelsButton" style="width: 50%">Labels</button>
</div>
<div id="uiContent"> </div>
<div id="labelsContent" class="hidden"> </div>
<div id="trackManagement">

Loading…
Cancel
Save