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'])]) label = _Label(self.db_labels[int(path['label_id'])])
boxes = [] boxes = []
frame = -1 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']: 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 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']), 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]) 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'])), tracked_box = _TrackedBox(xtl, ytl, xbr, ybr, int(box['frame']), strtobool(str(box['occluded'])),
int(box['z_order']), strtobool(str(box['outside']))) int(box['z_order']), strtobool(str(box['outside'])))
assert tracked_box.frame > frame assert tracked_box.frame > frame
frame = tracked_box.frame frame = tracked_box.frame
for attr in box['attributes']: for attr in box['attributes']:
@ -780,7 +795,7 @@ class _AnnotationForJob(_Annotation):
boxes.append(tracked_box) boxes.append(tracked_box)
else: else:
self.logger.error("init_from_client: ignore frame #%d " + 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 = [] attributes = []
for attr in path['attributes']: for attr in path['attributes']:
@ -790,7 +805,7 @@ class _AnnotationForJob(_Annotation):
attributes.append(attr) attributes.append(attr)
assert frame <= self.stop_frame 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) int(path['group_id']), boxes, attributes)
self.box_paths.append(box_path) self.box_paths.append(box_path)
@ -799,8 +814,23 @@ class _AnnotationForJob(_Annotation):
label = _Label(self.db_labels[int(path['label_id'])]) label = _Label(self.db_labels[int(path['label_id'])])
poly_shapes = [] poly_shapes = []
frame = -1 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']: 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 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]) 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'])), 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) poly_shapes.append(tracked_poly_shape)
else: else:
self.logger.error("init_from_client: ignore frame #%d " + 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 = [] attributes = []
for attr in path['attributes']: for attr in path['attributes']:
@ -826,7 +856,7 @@ class _AnnotationForJob(_Annotation):
attr = _Attribute(spec, str(attr['value'])) attr = _Attribute(spec, str(attr['value']))
attributes.append(attr) 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) int(path['group_id']), poly_shapes, attributes)
getattr(self, poly_path_type).append(poly_path) 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): class AttributeVal(models.Model):
# TODO: add a validator here to be sure that it corresponds to self.label # 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) spec = models.ForeignKey(AttributeSpec, on_delete=models.CASCADE)
value = models.CharField(max_length=64) value = models.CharField(max_length=64)
class Meta: class Meta:
@ -148,6 +149,7 @@ class Shape(models.Model):
abstract = True abstract = True
class BoundingBox(Shape): class BoundingBox(Shape):
id = models.BigAutoField(primary_key=True)
xtl = models.FloatField() xtl = models.FloatField()
ytl = models.FloatField() ytl = models.FloatField()
xbr = models.FloatField() xbr = models.FloatField()
@ -156,6 +158,7 @@ class BoundingBox(Shape):
abstract = True abstract = True
class PolyShape(Shape): class PolyShape(Shape):
id = models.BigAutoField(primary_key=True)
points = models.TextField() points = models.TextField()
class Meta: class Meta:
abstract = True abstract = True
@ -185,6 +188,7 @@ class LabeledPointsAttributeVal(AttributeVal):
points = models.ForeignKey(LabeledPoints, on_delete=models.CASCADE) points = models.ForeignKey(LabeledPoints, on_delete=models.CASCADE)
class ObjectPath(Annotation): class ObjectPath(Annotation):
id = models.BigAutoField(primary_key=True)
shapes = models.CharField(max_length=10, default='boxes') shapes = models.CharField(max_length=10, default='boxes')
class ObjectPathAttributeVal(AttributeVal): class ObjectPathAttributeVal(AttributeVal):

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

@ -1,17 +1,17 @@
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> 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 Subject: [PATCH] tmp
--- ---
.../engine/static/engine/js/3rdparty/svg.draggable.js | 1 + .../engine/static/engine/js/3rdparty/svg.draggable.js | 1 +
cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js | 17 +++++++++++++++-- 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.resize.js | 5 +++--
.../apps/engine/static/engine/js/3rdparty/svg.select.js | 1 + .../apps/engine/static/engine/js/3rdparty/svg.select.js | 5 ++++-
4 files changed, 20 insertions(+), 4 deletions(-) 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 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 --- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
+++ b/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 @@ @@ -109,6 +109,7 @@
@ -23,7 +23,7 @@ index d88abf5..06158f1 100644
var box = this.getBBox() var box = this.getBBox()
, p = this.transformPoint(e) , 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 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 --- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
+++ b/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 @@ @@ -18,6 +18,7 @@
@ -101,7 +101,7 @@ index 0c3b63d..fb5dc26 100644
// Calculate the difference between the mouseposition at start and now // Calculate the difference between the mouseposition at start and now
var txPt = this._extractPosition(event); 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 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 --- a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
+++ b/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 () { @@ -160,6 +160,7 @@ SelectHandler.prototype.drawPoints = function () {
@ -112,6 +112,32 @@ index 47e07bd..f1d0c02 100644
var x = ev.pageX || ev.touches[0].pageX; var x = ev.pageX || ev.touches[0].pageX;
var y = ev.pageY || ev.touches[0].pageY; var y = ev.pageY || ev.touches[0].pageY;
_this.el.fire('point', {x: x, y: y, i: k, event: ev}); _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 2.7.4

@ -322,8 +322,6 @@ class AnnotationParser {
Ignore all frames more then stop. Ignore all frames more then stop.
*/ */
let significant = keyFrame || frame === this._startFrame; let significant = keyFrame || frame === this._startFrame;
significant = significant && frame >= this._startFrame;
significant = significant && frame <= this._stopFrame;
if (significant) { if (significant) {
let attributeList = this._getAttributeList(shape, labelId); let attributeList = this._getAttributeList(shape, labelId);
@ -348,7 +346,7 @@ class AnnotationParser {
path.attributes = pathAttributes; path.attributes = pathAttributes;
if (type === 'boxes') { 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({ path.shapes.push({
frame: frame, frame: frame,
occluded: occluded, occluded: occluded,
@ -362,7 +360,7 @@ class AnnotationParser {
}); });
} }
else { 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({ path.shapes.push({
frame: frame, frame: frame,
occluded: occluded, occluded: occluded,

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

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

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

@ -1247,8 +1247,17 @@ class ShapeCollectionView {
view.erase(); 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) { for (let shape of collection.currentShapes) {
let model = shape.model; let model = shape.model;
let view = buildShapeView(model, buildShapeController(model), this._frameContent, this._UIContent); let view = buildShapeView(model, buildShapeController(model), this._frameContent, this._UIContent);
@ -1259,6 +1268,10 @@ class ShapeCollectionView {
view.subscribe(this); view.subscribe(this);
this._labelsContent.find(`.labelContentElement[label_id="${model.label}"]`).removeClass('hidden'); this._labelsContent.find(`.labelContentElement[label_id="${model.label}"]`).removeClass('hidden');
} }
parents.shapes.append(this._frameContent.node);
parents.uis.prepend(this._UIContent);
ShapeCollectionView.sortByZOrder(); ShapeCollectionView.sortByZOrder();
} }

@ -261,6 +261,75 @@ class ShapeCreatorView {
}.bind(this)); }.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() { _create() {
let sizeUI = null; let sizeUI = null;
switch(this._type) { switch(this._type) {
@ -302,7 +371,7 @@ class ShapeCreatorView {
this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({
'stroke-width': 0, 'stroke-width': 0,
}); });
createPolyEvents.call(this); this._createPolyEvents();
break; break;
case 'polygon': case 'polygon':
if (this._polyShapeSize && this._polyShapeSize < 3) { if (this._polyShapeSize && this._polyShapeSize < 3) {
@ -315,7 +384,7 @@ class ShapeCreatorView {
this._drawInstance = this._frameContent.polygon().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ this._drawInstance = this._frameContent.polygon().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / this._scale, 'stroke-width': STROKE_WIDTH / this._scale,
}); });
createPolyEvents.call(this); this._createPolyEvents();
break; break;
case 'polyline': case 'polyline':
if (this._polyShapeSize && this._polyShapeSize < 2) { if (this._polyShapeSize && this._polyShapeSize < 2) {
@ -328,7 +397,7 @@ class ShapeCreatorView {
this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / this._scale, 'stroke-width': STROKE_WIDTH / this._scale,
}); });
createPolyEvents.call(this); this._createPolyEvents();
break; break;
default: default:
throw Error(`Bad type found ${this._type}`); throw Error(`Bad type found ${this._type}`);
@ -368,73 +437,6 @@ class ShapeCreatorView {
this._drawInstance.attr({ this._drawInstance.attr({
'z_order': Number.MAX_SAFE_INTEGER, '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() { _rescaleDrawPoints() {
@ -477,7 +479,7 @@ class ShapeCreatorView {
this._type = model.defaultType; this._type = model.defaultType;
this._mode = model.defaultMode; this._mode = model.defaultMode;
if (this._type === 'box') { if (!['polygon', 'polyline', 'points'].includes(this._type)) {
this._drawAim(); this._drawAim();
} }
@ -486,10 +488,7 @@ class ShapeCreatorView {
this._create(); this._create();
} }
else { else {
if (this._type === 'box') { this._removeAim();
this._removeAim();
}
this._cancel = true; this._cancel = true;
this._createButton.text("Create Shape"); this._createButton.text("Create Shape");
document.oncontextmenu = null; document.oncontextmenu = null;
@ -526,7 +525,7 @@ class ShapeCreatorView {
this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale); this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale);
this._aim.y.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); this._drawInstance.attr('stroke-width', STROKE_WIDTH / this._scale);
} }
} }

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

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

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

@ -544,7 +544,6 @@ def _find_and_compress_images(upload_dir, output_dir, db_task, compress_quality,
filenames.sort() filenames.sort()
if len(filenames): if len(filenames):
compressed_names = []
for idx, name in enumerate(filenames): for idx, name in enumerate(filenames):
job.meta['status'] = 'Images are being compressed.. {}%'.format(idx * 100 // len(filenames)) job.meta['status'] = 'Images are being compressed.. {}%'.format(idx * 100 // len(filenames))
job.save_meta() 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 = image.transpose(Image.ROTATE_180)
image.save(compressed_name, quality=compress_quality, optimize=True) image.save(compressed_name, quality=compress_quality, optimize=True)
image.close() image.close()
compressed_names.append(compressed_name)
if compressed_name != name: if compressed_name != name:
os.remove(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): for frame, image_orig_path in enumerate(filenames):
image_dest_path = _get_frame_path(frame, output_dir) image_dest_path = _get_frame_path(frame, output_dir)

@ -375,11 +375,12 @@
</div> </div>
</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 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="uiContent"> </div>
<div id="labelsContent" class="hidden"> </div> <div id="labelsContent" class="hidden"> </div>
<div id="trackManagement"> <div id="trackManagement">

Loading…
Cancel
Save