From 6e64bb31f22241be86b56d856674b35b084ddab9 Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Wed, 12 Sep 2018 10:32:08 +0300 Subject: [PATCH] Data validation on client and server (#51) * Data validation on client and server * Consistent uploading methods for task and for job --- cvat/apps/engine/annotation.py | 44 +++++- .../static/engine/js/annotationParser.js | 6 +- .../engine/static/engine/js/shapeBuffer.js | 2 +- .../static/engine/js/shapeCollection.js | 2 +- cvat/apps/engine/static/engine/js/shapes.js | 127 +++++++++++++----- 5 files changed, 134 insertions(+), 47 deletions(-) diff --git a/cvat/apps/engine/annotation.py b/cvat/apps/engine/annotation.py index 234c2267..f6c28376 100644 --- a/cvat/apps/engine/annotation.py +++ b/cvat/apps/engine/annotation.py @@ -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) diff --git a/cvat/apps/engine/static/engine/js/annotationParser.js b/cvat/apps/engine/static/engine/js/annotationParser.js index 0f9ee9a7..92536516 100644 --- a/cvat/apps/engine/static/engine/js/annotationParser.js +++ b/cvat/apps/engine/static/engine/js/annotationParser.js @@ -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, diff --git a/cvat/apps/engine/static/engine/js/shapeBuffer.js b/cvat/apps/engine/static/engine/js/shapeBuffer.js index 8c0efe4f..42ef6bf5 100644 --- a/cvat/apps/engine/static/engine/js/shapeBuffer.js +++ b/cvat/apps/engine/static/engine/js/shapeBuffer.js @@ -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; diff --git a/cvat/apps/engine/static/engine/js/shapeCollection.js b/cvat/apps/engine/static/engine/js/shapeCollection.js index 121dd94c..bdb08126 100644 --- a/cvat/apps/engine/static/engine/js/shapeCollection.js +++ b/cvat/apps/engine/static/engine/js/shapeCollection.js @@ -1252,7 +1252,7 @@ class ShapeCollectionView { let parents = { uis: this._UIContent.parent(), shapes: this._frameContent.node.parentNode - } + }; this._frameContent.node.parent = null; this._UIContent.detach(); diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js index 40371245..e58d52f1 100644 --- a/cvat/apps/engine/static/engine/js/shapes.js +++ b/cvat/apps/engine/static/engine/js/shapes.js @@ -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; }