Incremental save of annotations (#120)

* using shapes id generated by a client
* removed md5.js, code cleanup
* fixed update_paths function; code cleanup;
* fixed import existing tasks without client_id
* added some sanity checks for cleint_id; improved annotation parser logic
* fixed dumping and uploading annotation if annotation task contains interpolation shapes
main
Andrey Zhavoronkov 7 years ago committed by Nikita Manovich
parent 8600970142
commit 7badd24cb0

@ -542,11 +542,15 @@ function uploadAnnotationRequest() {
return; return;
} }
const exportData = createExportContainer();
exportData.create = parsed;
exportData.pre_erase = true;
let asyncSave = function() { let asyncSave = function() {
$.ajax({ $.ajax({
url: '/save/annotation/task/' + window.cvat.dashboard.taskID, url: '/save/annotation/task/' + window.cvat.dashboard.taskID,
type: 'POST', type: 'POST',
data: JSON.stringify(parsed), data: JSON.stringify(exportData),
contentType: 'application/json', contentType: 'application/json',
success: function() { success: function() {
let message = 'Annotation successfully uploaded'; let message = 'Annotation successfully uploaded';

@ -11,7 +11,6 @@ import numpy as np
from scipy.optimize import linear_sum_assignment from scipy.optimize import linear_sum_assignment
from collections import OrderedDict from collections import OrderedDict
from distutils.util import strtobool from distutils.util import strtobool
from xml.dom import minidom
from xml.sax.saxutils import XMLGenerator from xml.sax.saxutils import XMLGenerator
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from PIL import Image from PIL import Image
@ -77,9 +76,16 @@ def save_job(jid, data):
Save new annotations for the job. Save new annotations for the job.
""" """
db_job = models.Job.objects.select_for_update().get(id=jid) db_job = models.Job.objects.select_for_update().get(id=jid)
annotation = _AnnotationForJob(db_job) annotation = _AnnotationForJob(db_job)
annotation.init_from_client(data) annotation.validate_data_from_client(data)
annotation.save_to_db() if data['pre_erase']:
annotation.delete_objs_from_db()
for action in ['create', 'update', 'delete']:
annotation.init_from_client(data[action])
annotation.save_to_db(action)
db_job.segment.task.updated_date = timezone.now() db_job.segment.task.updated_date = timezone.now()
db_job.segment.task.save() db_job.segment.task.save()
@ -97,16 +103,20 @@ def save_task(tid, data):
jid = segment.job_set.first().id jid = segment.job_set.first().id
start = segment.start_frame start = segment.start_frame
stop = segment.stop_frame stop = segment.stop_frame
splitted_data[jid] = { splitted_data[jid] = {}
"boxes": list(filter(lambda x: start <= int(x['frame']) <= stop, data['boxes'])), for action in ['create', 'update', 'delete']:
"polygons": list(filter(lambda x: start <= int(x['frame']) <= stop, data['polygons'])), splitted_data[jid][action] = {
"polylines": list(filter(lambda x: start <= int(x['frame']) <= stop, data['polylines'])), "boxes": list(filter(lambda x: start <= int(x['frame']) <= stop, data[action]['boxes'])),
"points": list(filter(lambda x: start <= int(x['frame']) <= stop, data['points'])), "polygons": list(filter(lambda x: start <= int(x['frame']) <= stop, data[action]['polygons'])),
"box_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data['box_paths'])), "polylines": list(filter(lambda x: start <= int(x['frame']) <= stop, data[action]['polylines'])),
"polygon_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data['polygon_paths'])), "points": list(filter(lambda x: start <= int(x['frame']) <= stop, data[action]['points'])),
"polyline_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data['polyline_paths'])), "box_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data[action]['box_paths'])),
"points_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data['points_paths'])), "polygon_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data[action]['polygon_paths'])),
} "polyline_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data[action]['polyline_paths'])),
"points_paths": list(filter(lambda x: len(list(filter(lambda y: (start <= int(y['frame']) <= stop) and (not y['outside']), x['shapes']))), data[action]['points_paths'])),
}
splitted_data[jid]['pre_erase'] = data['pre_erase']
for jid, _data in splitted_data.items(): for jid, _data in splitted_data.items():
save_job(jid, _data) save_job(jid, _data)
@ -133,13 +143,14 @@ class _Attribute:
self.value = str(value) self.value = str(value)
class _BoundingBox: class _BoundingBox:
def __init__(self, x0, y0, x1, y1, frame, occluded, z_order, attributes=None): def __init__(self, x0, y0, x1, y1, frame, occluded, z_order, client_id=None, attributes=None):
self.xtl = x0 self.xtl = x0
self.ytl = y0 self.ytl = y0
self.xbr = x1 self.xbr = x1
self.ybr = y1 self.ybr = y1
self.occluded = occluded self.occluded = occluded
self.z_order = z_order self.z_order = z_order
self.client_id = client_id
self.frame = frame self.frame = frame
self.attributes = attributes if attributes else [] self.attributes = attributes if attributes else []
@ -156,14 +167,14 @@ class _BoundingBox:
self.attributes.append(attr) self.attributes.append(attr)
class _LabeledBox(_BoundingBox): class _LabeledBox(_BoundingBox):
def __init__(self, label, x0, y0, x1, y1, frame, group_id, occluded, z_order, attributes=None): def __init__(self, label, x0, y0, x1, y1, frame, group_id, occluded, z_order, client_id=None, attributes=None):
super().__init__(x0, y0, x1, y1, frame, occluded, z_order, attributes) super().__init__(x0, y0, x1, y1, frame, occluded, z_order, client_id, attributes)
self.label = label self.label = label
self.group_id = group_id self.group_id = group_id
class _TrackedBox(_BoundingBox): class _TrackedBox(_BoundingBox):
def __init__(self, x0, y0, x1, y1, frame, occluded, z_order, outside, attributes=None): def __init__(self, x0, y0, x1, y1, frame, occluded, z_order, outside, attributes=None):
super().__init__(x0, y0, x1, y1, frame, occluded, z_order, attributes) super().__init__(x0, y0, x1, y1, frame, occluded, z_order, None, attributes)
self.outside = outside self.outside = outside
class _InterpolatedBox(_TrackedBox): class _InterpolatedBox(_TrackedBox):
@ -172,25 +183,26 @@ class _InterpolatedBox(_TrackedBox):
self.keyframe = keyframe self.keyframe = keyframe
class _PolyShape: class _PolyShape:
def __init__(self, points, frame, occluded, z_order, attributes=None): def __init__(self, points, frame, occluded, z_order, client_id=None, attributes=None):
self.points = points self.points = points
self.frame = frame
self.occluded = occluded self.occluded = occluded
self.z_order = z_order self.z_order = z_order
self.frame = frame self.client_id=client_id
self.attributes = attributes if attributes else [] self.attributes = attributes if attributes else []
def add_attribute(self, attr): def add_attribute(self, attr):
self.attributes.append(attr) self.attributes.append(attr)
class _LabeledPolyShape(_PolyShape): class _LabeledPolyShape(_PolyShape):
def __init__(self, label, points, frame, group_id, occluded, z_order, attributes=None): def __init__(self, label, points, frame, group_id, occluded, z_order, client_id=None, attributes=None):
super().__init__(points, frame, occluded, z_order, attributes) super().__init__(points, frame, occluded, z_order, client_id, attributes)
self.label = label self.label = label
self.group_id = group_id self.group_id = group_id
class _TrackedPolyShape(_PolyShape): class _TrackedPolyShape(_PolyShape):
def __init__(self, points, frame, occluded, z_order, outside, attributes=None): def __init__(self, points, frame, occluded, z_order, outside, attributes=None):
super().__init__(points, frame, occluded, z_order, attributes) super().__init__(points, frame, occluded, z_order, None, attributes)
self.outside = outside self.outside = outside
class _InterpolatedPolyShape(_TrackedPolyShape): class _InterpolatedPolyShape(_TrackedPolyShape):
@ -199,12 +211,13 @@ class _InterpolatedPolyShape(_TrackedPolyShape):
self.keyframe = keyframe self.keyframe = keyframe
class _BoxPath: class _BoxPath:
def __init__(self, label, start_frame, stop_frame, group_id, boxes=None, attributes=None): def __init__(self, label, start_frame, stop_frame, group_id, boxes=None, client_id=None, attributes=None):
self.label = label self.label = label
self.frame = start_frame self.frame = start_frame
self.group_id = group_id
self.stop_frame = stop_frame self.stop_frame = stop_frame
self.group_id = group_id
self.boxes = boxes if boxes else [] self.boxes = boxes if boxes else []
self.client_id = client_id
self.attributes = attributes if attributes else [] self.attributes = attributes if attributes else []
self._interpolated_boxes = [] self._interpolated_boxes = []
assert not self.boxes or self.boxes[-1].frame <= self.stop_frame assert not self.boxes or self.boxes[-1].frame <= self.stop_frame
@ -273,12 +286,13 @@ class _BoxPath:
self.attributes.append(attr) self.attributes.append(attr)
class _PolyPath: class _PolyPath:
def __init__(self, label, start_frame, stop_frame, group_id, shapes=None, attributes=None): def __init__(self, label, start_frame, stop_frame, group_id, shapes=None, client_id=None, attributes=None):
self.label = label self.label = label
self.frame = start_frame self.frame = start_frame
self.group_id = group_id
self.stop_frame = stop_frame self.stop_frame = stop_frame
self.group_id = group_id
self.shapes = shapes if shapes else [] self.shapes = shapes if shapes else []
self.client_id = client_id
self.attributes = attributes if attributes else [] self.attributes = attributes if attributes else []
self._interpolated_shapes = [] # ??? self._interpolated_shapes = [] # ???
@ -333,36 +347,82 @@ class _Annotation:
self.points = [] self.points = []
self.points_paths = [] self.points_paths = []
def get_max_client_id(self):
max_client_id = -1
def extract_client_id(shape):
return shape.client_id
if self.boxes:
max_client_id = max(max_client_id, (max(self.boxes, key=extract_client_id)).client_id)
if self.box_paths:
max_client_id = max(max_client_id, (max(self.box_paths, key=extract_client_id)).client_id)
if self.polygons:
max_client_id = max(max_client_id, (max(self.polygons, key=extract_client_id)).client_id)
if self.polygon_paths:
max_client_id = max(max_client_id, (max(self.polygon_paths, key=extract_client_id)).client_id)
if self.polylines:
max_client_id = max(max_client_id, (max(self.polylines, key=extract_client_id)).client_id)
if self.polyline_paths:
max_client_id = max(max_client_id, (max(self.polyline_paths, key=extract_client_id)).client_id)
if self.points:
max_client_id = max(max_client_id, (max(self.points, key=extract_client_id)).client_id)
if self.points_paths:
max_client_id = max(max_client_id, (max(self.points_paths, key=extract_client_id)).client_id)
return max_client_id
# Functions below used by dump functionality # Functions below used by dump functionality
def to_boxes(self): def to_boxes(self, start_client_id):
boxes = [] boxes = []
for path in self.box_paths: for path in self.box_paths:
for box in path.get_interpolated_boxes(): for box in path.get_interpolated_boxes():
if not box.outside: if not box.outside:
box = _LabeledBox(path.label, box.xtl, box.ytl, box.xbr, box.ybr, box = _LabeledBox(
box.frame, path.group_id, box.occluded, box.z_order, box.attributes + path.attributes) label=path.label,
x0=box.xtl, y0=box.ytl, x1=box.xbr, y1=box.ybr,
frame=box.frame,
group_id=path.group_id,
occluded=box.occluded,
z_order=box.z_order,
client_id=start_client_id,
attributes=box.attributes + path.attributes,
)
boxes.append(box) boxes.append(box)
start_client_id += 1
return self.boxes + boxes return self.boxes + boxes, start_client_id
def _to_poly_shapes(self, iter_attr_name): def _to_poly_shapes(self, iter_attr_name, start_client_id):
shapes = [] shapes = []
for path in getattr(self, iter_attr_name): for path in getattr(self, iter_attr_name):
for shape in path.get_interpolated_shapes(): for shape in path.get_interpolated_shapes():
if not shape.outside: if not shape.outside:
shape = _LabeledPolyShape(path.label, shape.points, shape.frame, path.group_id, shape = _LabeledPolyShape(
shape.occluded, shape.z_order, shape.attributes + path.attributes) label=path.label,
points=shape.points,
frame=shape.frame,
group_id=path.group_id,
occluded=shape.occluded,
z_order=shape.z_order,
client_id=start_client_id,
attributes=shape.attributes + path.attributes,
)
shapes.append(shape) shapes.append(shape)
return shapes start_client_id += 1
return shapes, start_client_id
def to_polygons(self): def to_polygons(self, start_client_id):
return self._to_poly_shapes('polygon_paths') + self.polygons polygons, client_id = self._to_poly_shapes('polygon_paths', start_client_id)
return polygons + self.polygons, client_id
def to_polylines(self): def to_polylines(self, start_client_id):
return self._to_poly_shapes('polyline_paths') + self.polylines polylines, client_id = self._to_poly_shapes('polyline_paths', start_client_id)
return polylines + self.polylines, client_id
def to_points(self): def to_points(self, start_client_id):
return self._to_poly_shapes('points_paths') + self.points points, client_id = self._to_poly_shapes('points_paths', start_client_id)
return points + self.points, client_id
def to_box_paths(self): def to_box_paths(self):
paths = [] paths = []
@ -372,7 +432,15 @@ class _Annotation:
box1 = copy.copy(box0) box1 = copy.copy(box0)
box1.outside = True box1.outside = True
box1.frame += 1 box1.frame += 1
path = _BoxPath(box.label, box.frame, box.frame + 1, box.group_id, [box0, box1], box.attributes) path = _BoxPath(
label=box.label,
start_frame=box.frame,
stop_frame=box.frame + 1,
group_id=box.group_id,
boxes=[box0, box1],
attributes=box.attributes,
client_id=box.client_id,
)
paths.append(path) paths.append(path)
return self.box_paths + paths return self.box_paths + paths
@ -385,7 +453,15 @@ class _Annotation:
shape1 = copy.copy(shape0) shape1 = copy.copy(shape0)
shape1.outside = True shape1.outside = True
shape1.frame += 1 shape1.frame += 1
path = _PolyPath(shape.label, shape.frame, shape.frame + 1, shape.group_id, [shape0, shape1], shape.attributes) path = _PolyPath(
label=shape.label,
start_frame=shape.frame,
stop_frame=shape.frame + 1,
group_id=shape.group_id,
shapes=[shape0, shape1],
client_id=shape.client_id,
attributes=shape.attributes,
)
paths.append(path) paths.append(path)
return paths return paths
@ -450,11 +526,16 @@ class _AnnotationForJob(_Annotation):
return list(merged_rows.values()) return list(merged_rows.values())
@staticmethod
def _clamp(value, min_value, max_value):
return max(min(value, max_value), min_value)
def _clamp_box(self, xtl, ytl, xbr, ybr, im_size): def _clamp_box(self, xtl, ytl, xbr, ybr, im_size):
xtl = max(min(xtl, im_size['width']), 0) xtl = self._clamp(xtl, 0, im_size['width'])
ytl = max(min(ytl, im_size['height']), 0) xbr = self._clamp(xbr, 0, im_size['width'])
xbr = max(min(xbr, im_size['width']), 0) ytl = self._clamp(ytl, 0, im_size['height'])
ybr = max(min(ybr, im_size['height']), 0) ybr = self._clamp(ybr, 0, im_size['height'])
return xtl, ytl, xbr, ybr return xtl, ytl, xbr, ybr
def _clamp_poly(self, points, im_size): def _clamp_poly(self, points, im_size):
@ -463,16 +544,17 @@ class _AnnotationForJob(_Annotation):
for p in points: for p in points:
p = p.split(',') p = p.split(',')
verified.append('{},{}'.format( verified.append('{},{}'.format(
max(min(float(p[0]), im_size['width']), 0), self._clamp(float(p[0]), 0, im_size['width']),
max(min(float(p[1]), im_size['height']), 0) self._clamp(float(p[1]), 0, im_size['height'])
)) ))
return ' '.join(verified) return ' '.join(verified)
def init_from_db(self): def init_from_db(self):
def get_values(shape_type): def get_values(shape_type):
if shape_type == 'polygons': if shape_type == 'polygons':
return [ return [
('id', 'frame', 'points', 'label_id', 'group_id', 'occluded', 'z_order', ('id', 'frame', 'points', 'label_id', 'group_id', 'occluded', 'z_order', 'client_id',
'labeledpolygonattributeval__value', 'labeledpolygonattributeval__spec_id', 'labeledpolygonattributeval__value', 'labeledpolygonattributeval__spec_id',
'labeledpolygonattributeval__id'), { 'labeledpolygonattributeval__id'), {
'attributes': [ 'attributes': [
@ -484,7 +566,7 @@ class _AnnotationForJob(_Annotation):
] ]
elif shape_type == 'polylines': elif shape_type == 'polylines':
return [ return [
('id', 'frame', 'points', 'label_id', 'group_id', 'occluded', 'z_order', ('id', 'frame', 'points', 'label_id', 'group_id', 'occluded', 'z_order', 'client_id',
'labeledpolylineattributeval__value', 'labeledpolylineattributeval__spec_id', 'labeledpolylineattributeval__value', 'labeledpolylineattributeval__spec_id',
'labeledpolylineattributeval__id'), { 'labeledpolylineattributeval__id'), {
'attributes': [ 'attributes': [
@ -496,7 +578,7 @@ class _AnnotationForJob(_Annotation):
] ]
elif shape_type == 'boxes': elif shape_type == 'boxes':
return [ return [
('id', 'frame', 'xtl', 'ytl', 'xbr', 'ybr', 'label_id', 'group_id', 'occluded', 'z_order', ('id', 'frame', 'xtl', 'ytl', 'xbr', 'ybr', 'label_id', 'group_id', 'occluded', 'z_order', 'client_id',
'labeledboxattributeval__value', 'labeledboxattributeval__spec_id', 'labeledboxattributeval__value', 'labeledboxattributeval__spec_id',
'labeledboxattributeval__id'), { 'labeledboxattributeval__id'), {
'attributes': [ 'attributes': [
@ -508,7 +590,7 @@ class _AnnotationForJob(_Annotation):
] ]
elif shape_type == 'points': elif shape_type == 'points':
return [ return [
('id', 'frame', 'points', 'label_id', 'group_id', 'occluded', 'z_order', ('id', 'frame', 'points', 'label_id', 'group_id', 'occluded', 'z_order', 'client_id',
'labeledpointsattributeval__value', 'labeledpointsattributeval__spec_id', 'labeledpointsattributeval__value', 'labeledpointsattributeval__spec_id',
'labeledpointsattributeval__id'), { 'labeledpointsattributeval__id'), {
'attributes': [ 'attributes': [
@ -528,11 +610,24 @@ class _AnnotationForJob(_Annotation):
for db_shape in db_shapes: for db_shape in db_shapes:
label = _Label(self.db_labels[db_shape.label_id]) label = _Label(self.db_labels[db_shape.label_id])
if shape_type == 'boxes': if shape_type == 'boxes':
shape = _LabeledBox(label, db_shape.xtl, db_shape.ytl, db_shape.xbr, db_shape.ybr, shape = _LabeledBox(label=label,
db_shape.frame, db_shape.group_id, db_shape.occluded, db_shape.z_order) x0=db_shape.xtl, y0=db_shape.ytl, x1=db_shape.xbr, y1=db_shape.ybr,
frame=db_shape.frame,
group_id=db_shape.group_id,
occluded=db_shape.occluded,
z_order=db_shape.z_order,
client_id=db_shape.client_id,
)
else: else:
shape = _LabeledPolyShape(label, db_shape.points, db_shape.frame, shape = _LabeledPolyShape(
db_shape.group_id, db_shape.occluded, db_shape.z_order) label=label,
points=db_shape.points,
frame=db_shape.frame,
group_id=db_shape.group_id,
occluded=db_shape.occluded,
z_order=db_shape.z_order,
client_id=db_shape.client_id,
)
for db_attr in db_shape.attributes: for db_attr in db_shape.attributes:
if db_attr.id != None: if db_attr.id != None:
spec = self.db_attributes[db_attr.spec_id] spec = self.db_attributes[db_attr.spec_id]
@ -540,8 +635,6 @@ class _AnnotationForJob(_Annotation):
shape.add_attribute(attr) shape.add_attribute(attr)
getattr(self, shape_type).append(shape) getattr(self, shape_type).append(shape)
db_paths = self.db_job.objectpath_set db_paths = self.db_job.objectpath_set
for shape in ['trackedpoints_set', 'trackedbox_set', 'trackedpolyline_set', 'trackedpolygon_set']: for shape in ['trackedpoints_set', 'trackedbox_set', 'trackedpolyline_set', 'trackedpolygon_set']:
db_paths.prefetch_related(shape) db_paths.prefetch_related(shape)
@ -549,7 +642,7 @@ class _AnnotationForJob(_Annotation):
'trackedpolygon_set__trackedpolygonattributeval_set', 'trackedpolyline_set__trackedpolylineattributeval_set']: 'trackedpolygon_set__trackedpolygonattributeval_set', 'trackedpolyline_set__trackedpolylineattributeval_set']:
db_paths.prefetch_related(shape_attr) db_paths.prefetch_related(shape_attr)
db_paths.prefetch_related('objectpathattributeval_set') db_paths.prefetch_related('objectpathattributeval_set')
db_paths = list (db_paths.values('id', 'frame', 'group_id', 'shapes', 'objectpathattributeval__spec_id', db_paths = list (db_paths.values('id', 'frame', 'group_id', 'shapes', 'client_id', 'objectpathattributeval__spec_id',
'objectpathattributeval__id', 'objectpathattributeval__value', 'objectpathattributeval__id', 'objectpathattributeval__value',
'trackedbox', 'trackedpolygon', 'trackedpolyline', 'trackedpoints', 'trackedbox', 'trackedpolygon', 'trackedpolyline', 'trackedpoints',
'trackedbox__id', 'label_id', 'trackedbox__xtl', 'trackedbox__ytl', 'trackedbox__id', 'label_id', 'trackedbox__xtl', 'trackedbox__ytl',
@ -667,7 +760,13 @@ class _AnnotationForJob(_Annotation):
for db_shape in db_path.shapes: for db_shape in db_path.shapes:
db_shape.attributes = list(set(db_shape.attributes)) db_shape.attributes = list(set(db_shape.attributes))
label = _Label(self.db_labels[db_path.label_id]) label = _Label(self.db_labels[db_path.label_id])
path = _BoxPath(label, db_path.frame, self.stop_frame, db_path.group_id) path = _BoxPath(
label=label,
start_frame=db_path.frame,
stop_frame=self.stop_frame,
group_id=db_path.group_id,
client_id=db_path.client_id,
)
for db_attr in db_path.attributes: for db_attr in db_path.attributes:
spec = self.db_attributes[db_attr.spec_id] spec = self.db_attributes[db_attr.spec_id]
attr = _Attribute(spec, db_attr.value) attr = _Attribute(spec, db_attr.value)
@ -675,8 +774,13 @@ class _AnnotationForJob(_Annotation):
frame = -1 frame = -1
for db_shape in db_path.shapes: for db_shape in db_path.shapes:
box = _TrackedBox(db_shape.xtl, db_shape.ytl, db_shape.xbr, db_shape.ybr, box = _TrackedBox(
db_shape.frame, db_shape.occluded, db_shape.z_order, db_shape.outside) x0=db_shape.xtl, y0=db_shape.ytl, x1=db_shape.xbr, y1=db_shape.ybr,
frame=db_shape.frame,
occluded=db_shape.occluded,
z_order=db_shape.z_order,
outside=db_shape.outside,
)
assert box.frame > frame assert box.frame > frame
frame = box.frame frame = box.frame
@ -695,7 +799,13 @@ class _AnnotationForJob(_Annotation):
for db_shape in db_path.shapes: for db_shape in db_path.shapes:
db_shape.attributes = list(set(db_shape.attributes)) db_shape.attributes = list(set(db_shape.attributes))
label = _Label(self.db_labels[db_path.label_id]) label = _Label(self.db_labels[db_path.label_id])
path = _PolyPath(label, db_path.frame, self.stop_frame, db_path.group_id) path = _PolyPath(
label=label,
start_frame=db_path.frame,
stop_frame= self.stop_frame,
group_id=db_path.group_id,
client_id=db_path.id,
)
for db_attr in db_path.attributes: for db_attr in db_path.attributes:
spec = self.db_attributes[db_attr.spec_id] spec = self.db_attributes[db_attr.spec_id]
attr = _Attribute(spec, db_attr.value) attr = _Attribute(spec, db_attr.value)
@ -703,7 +813,13 @@ class _AnnotationForJob(_Annotation):
frame = -1 frame = -1
for db_shape in db_path.shapes: for db_shape in db_path.shapes:
shape = _TrackedPolyShape(db_shape.points, db_shape.frame, db_shape.occluded, db_shape.z_order, db_shape.outside) shape = _TrackedPolyShape(
points=db_shape.points,
frame=db_shape.frame,
occluded=db_shape.occluded,
z_order=db_shape.z_order,
outside=db_shape.outside,
)
assert shape.frame > frame assert shape.frame > frame
frame = shape.frame frame = shape.frame
@ -731,8 +847,16 @@ class _AnnotationForJob(_Annotation):
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']), float(box['xbr']), float(box['ybr']),
image_meta['original_size'][frame_idx]) image_meta['original_size'][frame_idx])
labeled_box = _LabeledBox(label, xtl, ytl, xbr, ybr, int(box['frame']),
int(box['group_id']), strtobool(str(box['occluded'])), int(box['z_order'])) labeled_box = _LabeledBox(
label=label,
x0=xtl, y0=ytl, x1=xbr, y1=ybr,
frame=int(box['frame']),
group_id=int(box['group_id']),
occluded=strtobool(str(box['occluded'])),
z_order=int(box['z_order']),
client_id=int(box['client_id']),
)
for attr in box['attributes']: for attr in box['attributes']:
spec = self.db_attributes[int(attr['id'])] spec = self.db_attributes[int(attr['id'])]
@ -747,8 +871,15 @@ class _AnnotationForJob(_Annotation):
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])
labeled_poly_shape = _LabeledPolyShape(label, points, int(poly_shape['frame']), labeled_poly_shape = _LabeledPolyShape(
int(poly_shape['group_id']), poly_shape['occluded'], int(poly_shape['z_order'])) label=label,
points=points,
frame=int(poly_shape['frame']),
group_id=int(poly_shape['group_id']),
occluded=poly_shape['occluded'],
z_order=int(poly_shape['z_order']),
client_id=int(poly_shape['client_id']),
)
for attr in poly_shape['attributes']: for attr in poly_shape['attributes']:
spec = self.db_attributes[int(attr['id'])] spec = self.db_attributes[int(attr['id'])]
@ -781,9 +912,14 @@ class _AnnotationForJob(_Annotation):
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(
int(box['z_order']), strtobool(str(box['outside']))) x0=xtl, y0=ytl, x1=xbr, y1=ybr,
assert tracked_box.frame > frame frame=int(box['frame']),
occluded=strtobool(str(box['occluded'])),
z_order=int(box['z_order']),
outside=strtobool(str(box['outside'])),
)
assert tracked_box.frame > frame
frame = tracked_box.frame frame = tracked_box.frame
for attr in box['attributes']: for attr in box['attributes']:
@ -805,8 +941,14 @@ class _AnnotationForJob(_Annotation):
attributes.append(attr) attributes.append(attr)
assert frame <= self.stop_frame assert frame <= self.stop_frame
box_path = _BoxPath(label, min(list(map(lambda box: box.frame, boxes))), self.stop_frame, box_path = _BoxPath(label=label,
int(path['group_id']), boxes, attributes) start_frame=min(list(map(lambda box: box.frame, boxes))),
stop_frame=self.stop_frame,
group_id=int(path['group_id']),
boxes=boxes,
client_id=int(path['client_id']),
attributes=attributes,
)
self.box_paths.append(box_path) self.box_paths.append(box_path)
for poly_path_type in ['points_paths', 'polygon_paths', 'polyline_paths']: for poly_path_type in ['points_paths', 'polygon_paths', 'polyline_paths']:
@ -833,8 +975,13 @@ class _AnnotationForJob(_Annotation):
if int(poly_shape['frame']) <= self.stop_frame and int(poly_shape['frame']) >= self.start_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(
int(poly_shape['z_order']), strtobool(str(poly_shape['outside']))) points=points,
frame=int(poly_shape['frame']),
occluded=strtobool(str(poly_shape['occluded'])),
z_order=int(poly_shape['z_order']),
outside=strtobool(str(poly_shape['outside'])),
)
assert tracked_poly_shape.frame > frame assert tracked_poly_shape.frame > frame
frame = tracked_poly_shape.frame frame = tracked_poly_shape.frame
@ -856,8 +1003,15 @@ 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, min(list(map(lambda shape: shape.frame, poly_shapes))), self.stop_frame + 1, poly_path = _PolyPath(
int(path['group_id']), poly_shapes, attributes) label=label,
start_frame=min(list(map(lambda shape: shape.frame, poly_shapes))),
stop_frame=self.stop_frame + 1,
group_id=int(path['group_id']),
shapes=poly_shapes,
client_id=int(path['client_id']),
attributes=attributes,
)
getattr(self, poly_path_type).append(poly_path) getattr(self, poly_path_type).append(poly_path)
@ -898,7 +1052,12 @@ class _AnnotationForJob(_Annotation):
return models.TrackedPointsAttributeVal return models.TrackedPointsAttributeVal
def _save_paths_to_db(self): def _save_paths_to_db(self):
self.db_job.objectpath_set.all().delete() saved_path_ids = list(self.db_job.objectpath_set.values_list('id', 'client_id'))
saved_db_ids = []
saved_client_ids = []
for db_id, client_id in saved_path_ids:
saved_db_ids.append(db_id)
saved_client_ids.append(client_id)
for shape_type in ['polygon_paths', 'polyline_paths', 'points_paths', 'box_paths']: for shape_type in ['polygon_paths', 'polyline_paths', 'points_paths', 'box_paths']:
db_paths = [] db_paths = []
@ -906,12 +1065,17 @@ class _AnnotationForJob(_Annotation):
db_shapes = [] db_shapes = []
db_shape_attrvals = [] db_shape_attrvals = []
for path in getattr(self, shape_type): shapes = getattr(self, shape_type)
for path in shapes:
if path.client_id in saved_client_ids:
raise Exception('Trying to create new shape with existing client_id {}'.format(path.client_id))
db_path = models.ObjectPath() db_path = models.ObjectPath()
db_path.job = self.db_job db_path.job = self.db_job
db_path.label = self.db_labels[path.label.id] db_path.label = self.db_labels[path.label.id]
db_path.frame = path.frame db_path.frame = path.frame
db_path.group_id = path.group_id db_path.group_id = path.group_id
db_path.client_id = path.client_id
if shape_type == 'polygon_paths': if shape_type == 'polygon_paths':
db_path.shapes = 'polygons' db_path.shapes = 'polygons'
elif shape_type == 'polyline_paths': elif shape_type == 'polyline_paths':
@ -929,8 +1093,8 @@ class _AnnotationForJob(_Annotation):
db_attrval.value = attr.value db_attrval.value = attr.value
db_path_attrvals.append(db_attrval) db_path_attrvals.append(db_attrval)
shapes = path.boxes if hasattr(path, 'boxes') else path.shapes path_shapes = path.boxes if hasattr(path, 'boxes') else path.shapes
for shape in shapes: for shape in path_shapes:
db_shape = self._get_shape_class(shape_type)() db_shape = self._get_shape_class(shape_type)()
db_shape.track_id = len(db_paths) db_shape.track_id = len(db_paths)
if shape_type == 'box_paths': if shape_type == 'box_paths':
@ -962,6 +1126,7 @@ class _AnnotationForJob(_Annotation):
db_shapes.append(db_shape) db_shapes.append(db_shape)
db_paths.append(db_path) db_paths.append(db_path)
db_paths = models.ObjectPath.objects.bulk_create(db_paths) db_paths = models.ObjectPath.objects.bulk_create(db_paths)
if db_paths and db_paths[0].id == None: if db_paths and db_paths[0].id == None:
@ -970,13 +1135,13 @@ class _AnnotationForJob(_Annotation):
# for Postgres bulk_create will return objects with ids even ids # for Postgres bulk_create will return objects with ids even ids
# are auto incremented. Thus we will not be inside the 'if'. # are auto incremented. Thus we will not be inside the 'if'.
if shape_type == 'polygon_paths': if shape_type == 'polygon_paths':
db_paths = list(self.db_job.objectpath_set.filter(shapes="polygons")) db_paths = list(self.db_job.objectpath_set.exclude(id__in=saved_db_ids))
elif shape_type == 'polyline_paths': elif shape_type == 'polyline_paths':
db_paths = list(self.db_job.objectpath_set.filter(shapes="polylines")) db_paths = list(self.db_job.objectpath_set.exclude(id__in=saved_db_ids))
elif shape_type == 'box_paths': elif shape_type == 'box_paths':
db_paths = list(self.db_job.objectpath_set.filter(shapes="boxes")) db_paths = list(self.db_job.objectpath_set.exclude(id__in=saved_db_ids))
elif shape_type == 'points_paths': elif shape_type == 'points_paths':
db_paths = list(self.db_job.objectpath_set.filter(shapes="points")) db_paths = list(self.db_job.objectpath_set.exclude(id__in=saved_db_ids))
for db_attrval in db_path_attrvals: for db_attrval in db_path_attrvals:
db_attrval.track_id = db_paths[db_attrval.track_id].id db_attrval.track_id = db_paths[db_attrval.track_id].id
@ -985,6 +1150,7 @@ class _AnnotationForJob(_Annotation):
for db_shape in db_shapes: for db_shape in db_shapes:
db_shape.track_id = db_paths[db_shape.track_id].id db_shape.track_id = db_paths[db_shape.track_id].id
db_shapes_ids = list(self._get_shape_class(shape_type).objects.filter(track__job_id=self.db_job.id).values_list('id', flat=True))
db_shapes = self._get_shape_class(shape_type).objects.bulk_create(db_shapes) db_shapes = self._get_shape_class(shape_type).objects.bulk_create(db_shapes)
if db_shapes and db_shapes[0].id == None: if db_shapes and db_shapes[0].id == None:
@ -992,7 +1158,7 @@ class _AnnotationForJob(_Annotation):
# but it definetely doesn't work for Postgres. Need to say that # but it definetely doesn't work for Postgres. Need to say that
# for Postgres bulk_create will return objects with ids even ids # for Postgres bulk_create will return objects with ids even ids
# are auto incremented. Thus we will not be inside the 'if'. # are auto incremented. Thus we will not be inside the 'if'.
db_shapes = list(self._get_shape_class(shape_type).objects.filter(track__job_id=self.db_job.id)) db_shapes = list(self._get_shape_class(shape_type).objects.exclude(id__in=db_shapes_ids).filter(track__job_id=self.db_job.id))
for db_attrval in db_shape_attrvals: for db_attrval in db_shape_attrvals:
if shape_type == 'polygon_paths': if shape_type == 'polygon_paths':
@ -1021,15 +1187,27 @@ class _AnnotationForJob(_Annotation):
db_attrvals = [] db_attrvals = []
for shape_type in ['polygons', 'polylines', 'points', 'boxes']: for shape_type in ['polygons', 'polylines', 'points', 'boxes']:
self._get_shape_set(shape_type).all().delete()
db_shapes = [] db_shapes = []
db_attrvals = [] db_attrvals = []
for shape in getattr(self, shape_type): saved_shapes_ids = list(self._get_shape_class(shape_type).objects.filter(job_id=self.db_job.id).values_list('id', 'client_id'))
saved_client_ids = []
saved_db_ids = []
for db_id, client_id in saved_shapes_ids:
saved_db_ids.append(db_id)
saved_client_ids.append(client_id)
shapes = getattr(self, shape_type)
for shape in shapes:
if shape.client_id in saved_client_ids:
raise Exception('Trying to create new shape with existing client_id {}'.format(shape.client_id))
db_shape = self._get_shape_class(shape_type)() db_shape = self._get_shape_class(shape_type)()
db_shape.job = self.db_job db_shape.job = self.db_job
db_shape.label = self.db_labels[shape.label.id] db_shape.label = self.db_labels[shape.label.id]
db_shape.group_id = shape.group_id db_shape.group_id = shape.group_id
db_shape.client_id = shape.client_id
if shape_type == 'boxes': if shape_type == 'boxes':
db_shape.xtl = shape.xtl db_shape.xtl = shape.xtl
db_shape.ytl = shape.ytl db_shape.ytl = shape.ytl
@ -1065,7 +1243,8 @@ class _AnnotationForJob(_Annotation):
# but it definetely doesn't work for Postgres. Need to say that # but it definetely doesn't work for Postgres. Need to say that
# for Postgres bulk_create will return objects with ids even ids # for Postgres bulk_create will return objects with ids even ids
# are auto incremented. Thus we will not be inside the 'if'. # are auto incremented. Thus we will not be inside the 'if'.
db_shapes = list(self._get_shape_set(shape_type).all()) db_shapes = list(self._get_shape_set(shape_type).exclude(id__in=saved_db_ids))
for db_attrval in db_attrvals: for db_attrval in db_attrvals:
if shape_type == 'polygons': if shape_type == 'polygons':
@ -1079,10 +1258,53 @@ class _AnnotationForJob(_Annotation):
self._get_shape_attr_class(shape_type).objects.bulk_create(db_attrvals) self._get_shape_attr_class(shape_type).objects.bulk_create(db_attrvals)
def save_to_db(self): def _update_shapes_in_db(self):
self._save_shapes_to_db() self._delete_paths_from_db()
self._save_paths_to_db() self._save_paths_to_db()
def _update_paths_in_db(self):
self._delete_shapes_from_db()
self._save_shapes_to_db()
def _delete_shapes_from_db(self):
for shape_type in ['polygons', 'polylines', 'points', 'boxes']:
client_ids_to_delete = list(shape.client_id for shape in getattr(self, shape_type))
deleted = self._get_shape_set(shape_type).filter(client_id__in=client_ids_to_delete).delete()
class_name = 'engine.{}'.format(self._get_shape_class(shape_type).__name__)
if not (deleted[0] == 0 and len(client_ids_to_delete) == 0) and (class_name in deleted[1] and deleted[1][class_name] != len(client_ids_to_delete)):
raise Exception('Number of deleted object doesn\'t match with requested number')
def _delete_paths_from_db(self):
for shape_type in ['polygon_paths', 'polyline_paths', 'points_paths', 'box_paths']:
client_ids_to_delete = list(shape.client_id for shape in getattr(self, shape_type))
deleted = self.db_job.objectpath_set.filter(client_id__in=client_ids_to_delete).delete()
class_name = 'engine.ObjectPath'
if not (deleted[0] == 0 and len(client_ids_to_delete) == 0) and \
(class_name in deleted[1] and deleted[1][class_name] != len(client_ids_to_delete)):
raise Exception('Number of deleted object doesn\'t match with requested number')
def _delete_all_shapes_from_db(self):
for shape_type in ['polygons', 'polylines', 'points', 'boxes']:
self._get_shape_set(shape_type).all().delete()
def _delete_all_paths_from_db(self):
self.db_job.objectpath_set.all().delete()
def save_to_db(self, action):
if action == 'create':
self._save_shapes_to_db()
self._save_paths_to_db()
elif action == 'update':
self._update_shapes_in_db()
self._update_paths_in_db()
elif action == 'delete':
self._delete_shapes_from_db()
self._delete_paths_from_db()
def delete_objs_from_db(self):
self._delete_all_shapes_from_db()
self._delete_all_paths_from_db()
def to_client(self): def to_client(self):
data = { data = {
"boxes": [], "boxes": [],
@ -1097,6 +1319,7 @@ class _AnnotationForJob(_Annotation):
for box in self.boxes: for box in self.boxes:
data["boxes"].append({ data["boxes"].append({
"client_id": box.client_id,
"label_id": box.label.id, "label_id": box.label.id,
"group_id": box.group_id, "group_id": box.group_id,
"xtl": box.xtl, "xtl": box.xtl,
@ -1112,6 +1335,7 @@ class _AnnotationForJob(_Annotation):
for poly_type in ['polygons', 'polylines', 'points']: for poly_type in ['polygons', 'polylines', 'points']:
for poly in getattr(self, poly_type): for poly in getattr(self, poly_type):
data[poly_type].append({ data[poly_type].append({
"client_id": poly.client_id,
"label_id": poly.label.id, "label_id": poly.label.id,
"group_id": poly.group_id, "group_id": poly.group_id,
"points": poly.points, "points": poly.points,
@ -1123,6 +1347,7 @@ class _AnnotationForJob(_Annotation):
for box_path in self.box_paths: for box_path in self.box_paths:
data["box_paths"].append({ data["box_paths"].append({
"client_id": box_path.client_id,
"label_id": box_path.label.id, "label_id": box_path.label.id,
"group_id": box_path.group_id, "group_id": box_path.group_id,
"frame": box_path.frame, "frame": box_path.frame,
@ -1145,6 +1370,7 @@ class _AnnotationForJob(_Annotation):
for poly_path_type in ['polygon_paths', 'polyline_paths', 'points_paths']: for poly_path_type in ['polygon_paths', 'polyline_paths', 'points_paths']:
for poly_path in getattr(self, poly_path_type): for poly_path in getattr(self, poly_path_type):
data[poly_path_type].append({ data[poly_path_type].append({
"client_id": poly_path.client_id,
"label_id": poly_path.label.id, "label_id": poly_path.label.id,
"group_id": poly_path.group_id, "group_id": poly_path.group_id,
"frame": poly_path.frame, "frame": poly_path.frame,
@ -1163,6 +1389,25 @@ class _AnnotationForJob(_Annotation):
return data return data
def validate_data_from_client(self, data):
# check unique id for each object
client_ids = set()
def extract_and_check_clinet_id(shape):
if 'client_id' not in shape:
raise Exception('No client_id field in received data')
client_id = shape['client_id']
if client_id in client_ids:
raise Exception('More than one object has the same client_id {}'.format(client_id))
client_ids.add(client_id)
return client_id
shape_types = ['boxes', 'points', 'polygons', 'polylines', 'box_paths',
'points_paths', 'polygon_paths', 'polyline_paths']
for action in ['create', 'update', 'delete']:
for shape_type in shape_types:
for shape in data[action][shape_type]:
extract_and_check_clinet_id(shape)
class _AnnotationForSegment(_Annotation): class _AnnotationForSegment(_Annotation):
def __init__(self, db_segment): def __init__(self, db_segment):
@ -1651,23 +1896,26 @@ class _AnnotationForTask(_Annotation):
shapes["polygons"] = {} shapes["polygons"] = {}
shapes["polylines"] = {} shapes["polylines"] = {}
shapes["points"] = {} shapes["points"] = {}
boxes, max_client_id = self.to_boxes(self.get_max_client_id() + 1)
for box in self.to_boxes(): for box in boxes:
if box.frame not in shapes["boxes"]: if box.frame not in shapes["boxes"]:
shapes["boxes"][box.frame] = [] shapes["boxes"][box.frame] = []
shapes["boxes"][box.frame].append(box) shapes["boxes"][box.frame].append(box)
for polygon in self.to_polygons(): polygons, max_client_id = self.to_polygons(max_client_id)
for polygon in polygons:
if polygon.frame not in shapes["polygons"]: if polygon.frame not in shapes["polygons"]:
shapes["polygons"][polygon.frame] = [] shapes["polygons"][polygon.frame] = []
shapes["polygons"][polygon.frame].append(polygon) shapes["polygons"][polygon.frame].append(polygon)
for polyline in self.to_polylines(): polylines, max_client_id = self.to_polylines(max_client_id)
for polyline in polylines:
if polyline.frame not in shapes["polylines"]: if polyline.frame not in shapes["polylines"]:
shapes["polylines"][polyline.frame] = [] shapes["polylines"][polyline.frame] = []
shapes["polylines"][polyline.frame].append(polyline) shapes["polylines"][polyline.frame].append(polyline)
for points in self.to_points(): points, max_client_id = self.to_points(max_client_id)
for points in points:
if points.frame not in shapes["points"]: if points.frame not in shapes["points"]:
shapes["points"][points.frame] = [] shapes["points"][points.frame] = []
shapes["points"][points.frame].append(points) shapes["points"][points.frame].append(points)
@ -1707,7 +1955,8 @@ class _AnnotationForTask(_Annotation):
("ytl", "{:.2f}".format(shape.ytl)), ("ytl", "{:.2f}".format(shape.ytl)),
("xbr", "{:.2f}".format(shape.xbr)), ("xbr", "{:.2f}".format(shape.xbr)),
("ybr", "{:.2f}".format(shape.ybr)), ("ybr", "{:.2f}".format(shape.ybr)),
("occluded", str(int(shape.occluded))) ("occluded", str(int(shape.occluded))),
("client_id", str(shape.client_id)),
]) ])
if db_task.z_order: if db_task.z_order:
dump_dict['z_order'] = str(shape.z_order) dump_dict['z_order'] = str(shape.z_order)
@ -1726,7 +1975,8 @@ class _AnnotationForTask(_Annotation):
"{:.2f}".format(float(p.split(',')[1])) "{:.2f}".format(float(p.split(',')[1]))
)) for p in shape.points.split(' ')) )) for p in shape.points.split(' '))
)), )),
("occluded", str(int(shape.occluded))) ("occluded", str(int(shape.occluded))),
("client_id", str(shape.client_id)),
]) ])
if db_task.z_order: if db_task.z_order:
@ -1773,7 +2023,8 @@ class _AnnotationForTask(_Annotation):
for path in path_list: for path in path_list:
dump_dict = OrderedDict([ dump_dict = OrderedDict([
("id", str(path_idx)), ("id", str(path_idx)),
("label", path.label.name) ("label", path.label.name),
("client_id", str(path.client_id)),
]) ])
if path.group_id: if path.group_id:
dump_dict['group_id'] = str(path.group_id) dump_dict['group_id'] = str(path.group_id)

@ -0,0 +1,38 @@
# Generated by Django 2.0.9 on 2018-10-11 12:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0009_auto_20180917_1424'),
]
operations = [
migrations.AddField(
model_name='labeledbox',
name='client_id',
field=models.BigIntegerField(default=-1),
),
migrations.AddField(
model_name='labeledpoints',
name='client_id',
field=models.BigIntegerField(default=-1),
),
migrations.AddField(
model_name='labeledpolygon',
name='client_id',
field=models.BigIntegerField(default=-1),
),
migrations.AddField(
model_name='labeledpolyline',
name='client_id',
field=models.BigIntegerField(default=-1),
),
migrations.AddField(
model_name='objectpath',
name='client_id',
field=models.BigIntegerField(default=-1),
),
]

@ -139,6 +139,7 @@ class Annotation(models.Model):
label = models.ForeignKey(Label, on_delete=models.CASCADE) label = models.ForeignKey(Label, on_delete=models.CASCADE)
frame = models.PositiveIntegerField() frame = models.PositiveIntegerField()
group_id = models.PositiveIntegerField(default=0) group_id = models.PositiveIntegerField(default=0)
client_id = models.BigIntegerField(default=-1)
class Meta: class Meta:
abstract = True abstract = True

@ -1,280 +0,0 @@
/*
* JavaScript MD5
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/* global define */
;(function ($) {
'use strict'
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safeAdd (x, y) {
var lsw = (x & 0xffff) + (y & 0xffff)
var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
return (msw << 16) | (lsw & 0xffff)
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bitRotateLeft (num, cnt) {
return (num << cnt) | (num >>> (32 - cnt))
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5cmn (q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
}
function md5ff (a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t)
}
function md5gg (a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t)
}
function md5hh (a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t)
}
function md5ii (a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t)
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binlMD5 (x, len) {
/* append padding */
x[len >> 5] |= 0x80 << (len % 32)
x[((len + 64) >>> 9 << 4) + 14] = len
var i
var olda
var oldb
var oldc
var oldd
var a = 1732584193
var b = -271733879
var c = -1732584194
var d = 271733878
for (i = 0; i < x.length; i += 16) {
olda = a
oldb = b
oldc = c
oldd = d
a = md5ff(a, b, c, d, x[i], 7, -680876936)
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
b = md5gg(b, c, d, a, x[i], 20, -373897302)
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
d = md5hh(d, a, b, c, x[i], 11, -358537222)
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
a = md5ii(a, b, c, d, x[i], 6, -198630844)
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
a = safeAdd(a, olda)
b = safeAdd(b, oldb)
c = safeAdd(c, oldc)
d = safeAdd(d, oldd)
}
return [a, b, c, d]
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr (input) {
var i
var output = ''
var length32 = input.length * 32
for (i = 0; i < length32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff)
}
return output
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl (input) {
var i
var output = []
output[(input.length >> 2) - 1] = undefined
for (i = 0; i < output.length; i += 1) {
output[i] = 0
}
var length8 = input.length * 8
for (i = 0; i < length8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32)
}
return output
}
/*
* Calculate the MD5 of a raw string
*/
function rstrMD5 (s) {
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8))
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstrHMACMD5 (key, data) {
var i
var bkey = rstr2binl(key)
var ipad = []
var opad = []
var hash
ipad[15] = opad[15] = undefined
if (bkey.length > 16) {
bkey = binlMD5(bkey, key.length * 8)
}
for (i = 0; i < 16; i += 1) {
ipad[i] = bkey[i] ^ 0x36363636
opad[i] = bkey[i] ^ 0x5c5c5c5c
}
hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
return binl2rstr(binlMD5(opad.concat(hash), 512 + 128))
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex (input) {
var hexTab = '0123456789abcdef'
var output = ''
var x
var i
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i)
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f)
}
return output
}
/*
* Encode a string as utf-8
*/
function str2rstrUTF8 (input) {
return unescape(encodeURIComponent(input))
}
/*
* Take string arguments and return either raw or hex encoded strings
*/
function rawMD5 (s) {
return rstrMD5(str2rstrUTF8(s))
}
function hexMD5 (s) {
return rstr2hex(rawMD5(s))
}
function rawHMACMD5 (k, d) {
return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d))
}
function hexHMACMD5 (k, d) {
return rstr2hex(rawHMACMD5(k, d))
}
function md5 (string, key, raw) {
if (!key) {
if (!raw) {
return hexMD5(string)
}
return rawMD5(string)
}
if (!raw) {
return hexHMACMD5(key, string)
}
return rawHMACMD5(key, string)
}
if (typeof define === 'function' && define.amd) {
define(function () {
return md5
})
} else if (typeof module === 'object' && module.exports) {
module.exports = md5
} else {
$.md5 = md5
}
})(this)

@ -15,6 +15,7 @@ class AnnotationParser {
this._flipped = job.flipped; this._flipped = job.flipped;
this._im_meta = job.image_meta_data; this._im_meta = job.image_meta_data;
this._labelsInfo = labelsInfo; this._labelsInfo = labelsInfo;
this._client_id_set = new Set();
} }
_xmlParseError(parsedXML) { _xmlParseError(parsedXML) {
@ -131,7 +132,8 @@ class AnnotationParser {
let result = []; let result = [];
for (let track of tracks) { for (let track of tracks) {
let label = track.getAttribute('label'); let label = track.getAttribute('label');
let group_id = track.getAttribute('group_id') || "0"; let group_id = track.getAttribute('group_id') || '0';
let client_id = track.getAttribute('client_id') || '-1';
let labelId = this._labelsInfo.labelIdOf(label); let labelId = this._labelsInfo.labelIdOf(label);
if (labelId === null) { if (labelId === null) {
throw Error(`An unknown label found in the annotation file: ${label}`); throw Error(`An unknown label found in the annotation file: ${label}`);
@ -149,6 +151,7 @@ class AnnotationParser {
!+shapes[0].getAttribute('outside') && +shapes[1].getAttribute('outside')) { !+shapes[0].getAttribute('outside') && +shapes[1].getAttribute('outside')) {
shapes[0].setAttribute('label', label); shapes[0].setAttribute('label', label);
shapes[0].setAttribute('group_id', group_id); shapes[0].setAttribute('group_id', group_id);
shapes[0].setAttribute('client_id', client_id);
result.push(shapes[0]); result.push(shapes[0]);
} }
} }
@ -157,6 +160,17 @@ class AnnotationParser {
return result; return result;
} }
_updateClientIds(data) {
let maxId = Math.max(-1, ...Array.from(this._client_id_set));
for (const shape_type in data) {
for (const shape of data[shape_type]) {
if (shape.client_id === -1) {
shape.client_id = ++maxId;
}
}
}
}
_parseAnnotationData(xml) { _parseAnnotationData(xml) {
let data = { let data = {
boxes: [], boxes: [],
@ -209,6 +223,15 @@ class AnnotationParser {
throw Error('An unknown label found in the annotation file: ' + shape.getAttribute('label')); throw Error('An unknown label found in the annotation file: ' + shape.getAttribute('label'));
} }
let client_id = parseInt(shape.getAttribute('client_id') || '-1');
if (client_id !== -1) {
if (this._client_id_set.has(client_id)) {
throw Error('More than one shape has the same client_id attribute');
}
this._client_id_set.add(client_id);
}
let attributeList = this._getAttributeList(shape, labelId); let attributeList = this._getAttributeList(shape, labelId);
if (shape_type === 'boxes') { if (shape_type === 'boxes') {
@ -224,6 +247,7 @@ class AnnotationParser {
ybr: ybr, ybr: ybr,
z_order: z_order, z_order: z_order,
attributes: attributeList, attributes: attributeList,
client_id: client_id,
}); });
} }
else { else {
@ -236,6 +260,7 @@ class AnnotationParser {
occluded: occluded, occluded: occluded,
z_order: z_order, z_order: z_order,
attributes: attributeList, attributes: attributeList,
client_id: client_id,
}); });
} }
} }
@ -255,7 +280,8 @@ class AnnotationParser {
let tracks = xml.getElementsByTagName('track'); let tracks = xml.getElementsByTagName('track');
for (let track of tracks) { for (let track of tracks) {
let labelId = this._labelsInfo.labelIdOf(track.getAttribute('label')); let labelId = this._labelsInfo.labelIdOf(track.getAttribute('label'));
let groupId = track.getAttribute('group_id') || "0"; let groupId = track.getAttribute('group_id') || '0';
let client_id = parseInt(track.getAttribute('client_id') || '-1');
if (labelId === null) { if (labelId === null) {
throw Error('An unknown label found in the annotation file: ' + name); throw Error('An unknown label found in the annotation file: ' + name);
} }
@ -307,9 +333,18 @@ class AnnotationParser {
group_id: +groupId, group_id: +groupId,
frame: +parsed[type][0].getAttribute('frame'), frame: +parsed[type][0].getAttribute('frame'),
attributes: [], attributes: [],
shapes: [] shapes: [],
client_id: client_id,
}; };
if (client_id !== -1) {
if (this._client_id_set.has(client_id)) {
throw Error('More than one shape has the same client_id attribute');
}
this._client_id_set.add(client_id);
}
for (let shape of parsed[type]) { for (let shape of parsed[type]) {
let keyFrame = +shape.getAttribute('keyframe'); let keyFrame = +shape.getAttribute('keyframe');
let outside = +shape.getAttribute('outside'); let outside = +shape.getAttribute('outside');
@ -381,7 +416,12 @@ class AnnotationParser {
return data; return data;
} }
_reset() {
this._client_id_set.clear();
}
parse(text) { parse(text) {
this._reset();
let xml = this._parser.parseFromString(text, 'text/xml'); let xml = this._parser.parseFromString(text, 'text/xml');
let parseerror = this._xmlParseError(xml); let parseerror = this._xmlParseError(xml);
if (parseerror.length) { if (parseerror.length) {
@ -390,6 +430,8 @@ class AnnotationParser {
let interpolationData = this._parseInterpolationData(xml); let interpolationData = this._parseInterpolationData(xml);
let annotationData = this._parseAnnotationData(xml); let annotationData = this._parseAnnotationData(xml);
return Object.assign({}, annotationData, interpolationData); let data = Object.assign({}, annotationData, interpolationData);
this._updateClientIds(data);
return data;
} }
} }

@ -105,7 +105,7 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) {
let shapeCollectionView = new ShapeCollectionView(shapeCollectionModel, shapeCollectionController); let shapeCollectionView = new ShapeCollectionView(shapeCollectionModel, shapeCollectionController);
window.cvat.data = { window.cvat.data = {
get: () => shapeCollectionModel.export(), get: () => shapeCollectionModel.exportAll(),
set: (data) => { set: (data) => {
shapeCollectionModel.empty(); shapeCollectionModel.empty();
shapeCollectionModel.import(data); shapeCollectionModel.import(data);
@ -689,11 +689,11 @@ function saveAnnotation(shapeCollectionModel, job) {
'points count': totalStat.points.annotation + totalStat.points.interpolation, 'points count': totalStat.points.annotation + totalStat.points.interpolation,
}); });
let exportedData = shapeCollectionModel.export(); const exportedData = shapeCollectionModel.export();
let annotationLogs = Logger.getLogs(); const annotationLogs = Logger.getLogs();
const data = { const data = {
annotation: exportedData, annotation: JSON.stringify(exportedData),
logs: JSON.stringify(annotationLogs.export()), logs: JSON.stringify(annotationLogs.export()),
}; };
@ -702,6 +702,7 @@ function saveAnnotation(shapeCollectionModel, job) {
saveJobRequest(job.jobid, data, () => { saveJobRequest(job.jobid, data, () => {
// success // success
shapeCollectionModel.reset_state();
shapeCollectionModel.updateHash(); shapeCollectionModel.updateHash();
saveButton.text('Success!'); saveButton.text('Success!');
setTimeout(() => { setTimeout(() => {

@ -4,7 +4,10 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/* exported confirm showMessage showOverlay dumpAnnotationRequest */ /* exported confirm showMessage showOverlay dumpAnnotationRequest ExportType
createExportContainer getExportTargetContainer
*/
"use strict"; "use strict";
Math.clamp = function(x, min, max) { Math.clamp = function(x, min, max) {
@ -160,6 +163,81 @@ function dumpAnnotationRequest(dumpButton, taskID) {
} }
} }
const ExportType = Object.freeze({
'create': 0,
'update': 1,
'delete': 2,
});
function createExportContainer() {
const container = {};
Object.keys(ExportType).forEach( action => {
container[action] = {
"boxes": [],
"box_paths": [],
"points": [],
"points_paths": [],
"polygons": [],
"polygon_paths": [],
"polylines": [],
"polyline_paths": [],
};
});
container.pre_erase = false;
return container;
}
function getExportTargetContainer(export_type, shape_type, container) {
let shape_container_target = undefined;
let export_action_container = undefined;
switch (export_type) {
case ExportType.create:
export_action_container = container.create;
break;
case ExportType.update:
export_action_container = container.update;
break;
case ExportType.delete:
export_action_container = container.delete;
break;
default:
throw Error('Unexpected export type');
}
switch (shape_type) {
case 'annotation_box':
shape_container_target = export_action_container.boxes;
break;
case 'interpolation_box':
shape_container_target = export_action_container.box_paths;
break;
case 'annotation_points':
shape_container_target = export_action_container.points;
break;
case 'interpolation_points':
shape_container_target = export_action_container.points_paths;
break;
case 'annotation_polygon':
shape_container_target = export_action_container.polygons;
break;
case 'interpolation_polygon':
shape_container_target = export_action_container.polygon_paths;
break;
case 'annotation_polyline':
shape_container_target = export_action_container.polylines;
break;
case 'interpolation_polyline':
shape_container_target = export_action_container.polyline_paths;
break;
default:
throw Error('Undefined shape type');
}
return shape_container_target;
}
/* These HTTP methods do not require CSRF protection */ /* These HTTP methods do not require CSRF protection */
function csrfSafeMethod(method) { function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));

@ -51,6 +51,8 @@ class ShapeCollectionModel extends Listener {
this._colorIdx = 0; this._colorIdx = 0;
this._filter = new FilterModel(() => this.update()); this._filter = new FilterModel(() => this.update());
this._splitter = new ShapeSplitter(); this._splitter = new ShapeSplitter();
this._erased = false;
this._initialShapes = {};
} }
_nextIdx() { _nextIdx() {
@ -237,49 +239,46 @@ class ShapeCollectionModel extends Listener {
return this; return this;
} }
reset_state() {
this._erased = false;
}
export() { export() {
let response = { const response = createExportContainer();
"boxes": [], response.pre_erase = this._erased;
"box_paths": [],
"points": [], for (const shape of this._shapes) {
"points_paths": [], let target_export_container = undefined;
"polygons": [], if (!shape._removed) {
"polygon_paths": [], if (!(shape.id in this._initialShapes) || this._erased) {
"polylines": [], target_export_container = getExportTargetContainer(ExportType.create, shape.type, response);
"polyline_paths": [], } else if (JSON.stringify(this._initialShapes[shape.id]) !== JSON.stringify(shape.export())) {
}; target_export_container = getExportTargetContainer(ExportType.update, shape.type, response);
} else {
for (let shape of this._shapes) { continue;
if (shape.removed) continue; }
switch (shape.type) {
case 'annotation_box':
response.boxes.push(shape.export());
break;
case 'interpolation_box':
response.box_paths.push(shape.export());
break;
case 'annotation_points':
response.points.push(shape.export());
break;
case 'interpolation_points':
response.points_paths.push(shape.export());
break;
case 'annotation_polygon':
response.polygons.push(shape.export());
break;
case 'interpolation_polygon':
response.polygon_paths.push(shape.export());
break;
case 'annotation_polyline':
response.polylines.push(shape.export());
break;
case 'interpolation_polyline':
response.polyline_paths.push(shape.export());
} }
else if (shape.id in this._initialShapes && !this._erased) {
// TODO in this case need push only id
target_export_container = getExportTargetContainer(ExportType.delete, shape.type, response);
}
else {
continue;
}
target_export_container.push(shape.export());
} }
return response;
}
return JSON.stringify(response); exportAll() {
const response = createExportContainer();
for (const shape of this._shapes) {
if (!shape._removed) {
getExportTargetContainer(ExportType.create, shape.type, response).push(shape.export());
}
}
return response.create;
} }
find(direction) { find(direction) {
@ -338,11 +337,30 @@ class ShapeCollectionModel extends Listener {
} }
hasUnsavedChanges() { hasUnsavedChanges() {
return md5(this.export()) !== this._hash; const exportData = this.export();
for (const actionType in ExportType) {
for (const shapes of Object.values(exportData[actionType])) {
if (shapes.length) {
return true;
}
}
}
return exportData.pre_erase;
} }
updateHash() { updateHash() {
this._hash = md5(this.export()); this._initialShapes = {};
if (this._erased) {
return this;
}
for (const shape of this._shapes) {
if (!shape.removed) {
this._initialShapes[shape.id] = shape.export();
}
}
return this; return this;
} }
@ -352,11 +370,26 @@ class ShapeCollectionModel extends Listener {
this._shapes = []; this._shapes = [];
this._idx = 0; this._idx = 0;
this._colorIdx = 0; this._colorIdx = 0;
this._erased = true;
this._interpolate(); this._interpolate();
} }
add(data, type) { add(data, type) {
let model = buildShapeModel(data, type, this._nextIdx(), this.nextColor()); let id = null;
if (!('client_id' in data)) {
id = this._nextIdx();
}
else if (data.client_id === -1 ) {
this._erased = true;
id = this._nextIdx();
}
else {
id = data.client_id;
this._idx = Math.max(this._idx, id) + 1;
}
let model = buildShapeModel(data, type, id, this.nextColor());
if (type.startsWith('interpolation')) { if (type.startsWith('interpolation')) {
this._interpolationShapes.push(model); this._interpolationShapes.push(model);
} }

@ -262,4 +262,4 @@ class ShapeGrouperView {
} }
} }
} }
} }

@ -14,6 +14,7 @@ const AREA_TRESHOLD = 9;
const TEXT_MARGIN = 10; const TEXT_MARGIN = 10;
/******************************** SHAPE MODELS ********************************/ /******************************** SHAPE MODELS ********************************/
class ShapeModel extends Listener { class ShapeModel extends Listener {
constructor(data, positions, type, id, color) { constructor(data, positions, type, id, color) {
super('onShapeUpdate', () => this ); super('onShapeUpdate', () => this );
@ -472,6 +473,7 @@ class ShapeModel extends Listener {
this.removed = false; this.removed = false;
}, () => { }, () => {
this.removed = true; this.removed = true;
}, window.cvat.player.frames.current); }, window.cvat.player.frames.current);
// End of undo/redo code // End of undo/redo code
} }
@ -490,6 +492,7 @@ class ShapeModel extends Listener {
if (value) { if (value) {
this._active = false; this._active = false;
} }
this._removed = value; this._removed = value;
this.notify('remove'); this.notify('remove');
} }
@ -757,6 +760,7 @@ class BoxModel extends ShapeModel {
} }
return Object.assign({}, this._positions[this._frame], { return Object.assign({}, this._positions[this._frame], {
client_id: this._id,
attributes: immutableAttributes, attributes: immutableAttributes,
label_id: this._label, label_id: this._label,
group_id: this._groupId, group_id: this._groupId,
@ -765,6 +769,7 @@ class BoxModel extends ShapeModel {
} }
else { else {
let boxPath = { let boxPath = {
client_id: this._id,
label_id: this._label, label_id: this._label,
group_id: this._groupId, group_id: this._groupId,
frame: this._frame, frame: this._frame,
@ -862,7 +867,6 @@ 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], {
@ -951,6 +955,7 @@ class PolyShapeModel extends ShapeModel {
} }
return Object.assign({}, this._positions[this._frame], { return Object.assign({}, this._positions[this._frame], {
client_id: this._id,
attributes: immutableAttributes, attributes: immutableAttributes,
label_id: this._label, label_id: this._label,
group_id: this._groupId, group_id: this._groupId,
@ -959,6 +964,7 @@ class PolyShapeModel extends ShapeModel {
} }
else { else {
let polyPath = { let polyPath = {
client_id: this._id,
label_id: this._label, label_id: this._label,
group_id: this._groupId, group_id: this._groupId,
frame: this._frame, frame: this._frame,
@ -1012,16 +1018,7 @@ class PolyShapeModel extends ShapeModel {
} }
static convertNumberArrayToString(arrayPoints) { static convertNumberArrayToString(arrayPoints) {
let serializedPoints = ''; return arrayPoints.map(point => `${point.x},${point.y}`).join(' ');
for (let point of arrayPoints) {
serializedPoints += `${point.x},${point.y} `;
}
let len = serializedPoints.length;
if (len) {
serializedPoints = serializedPoints.substring(0, len - 1);
}
return serializedPoints;
} }
static importPositions(positions) { static importPositions(positions) {
@ -3173,8 +3170,6 @@ class PointsView extends PolyShapeView {
} }
} }
function buildShapeModel(data, type, idx, color) { function buildShapeModel(data, type, idx, color) {
switch (type) { switch (type) {
case 'interpolation_box': case 'interpolation_box':

@ -349,4 +349,4 @@ class Config {
get settings() { get settings() {
return JSON.parse(JSON.stringify(this._settings)); return JSON.parse(JSON.stringify(this._settings));
} }
} }

@ -19,7 +19,6 @@
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.resize.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.resize.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.draggable.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.draggable.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.select.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.select.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/md5.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/defiant.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/3rdparty/defiant.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/jquery-3.3.1.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/3rdparty/jquery-3.3.1.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/js.cookie.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/3rdparty/js.cookie.js' %}"></script>

@ -260,7 +260,6 @@ def save_annotation_for_job(request, jid):
return HttpResponse() return HttpResponse()
@login_required @login_required
@permission_required(perm=['engine.view_task', 'engine.change_annotation'], raise_exception=True) @permission_required(perm=['engine.view_task', 'engine.change_annotation'], raise_exception=True)
def save_annotation_for_task(request, tid): def save_annotation_for_task(request, tid):

@ -105,20 +105,28 @@ def make_image_list(path_to_data):
def convert_to_cvat_format(data): def convert_to_cvat_format(data):
def create_anno_container():
return {
"boxes": [],
"polygons": [],
"polylines": [],
"points": [],
"box_paths": [],
"polygon_paths": [],
"polyline_paths": [],
"points_paths": [],
}
result = { result = {
"boxes": [], 'create': create_anno_container(),
"polygons": [], 'update': create_anno_container(),
"polylines": [], 'delete': create_anno_container(),
"points": [], 'pre_erase': True,
"box_paths": [],
"polygon_paths": [],
"polyline_paths": [],
"points_paths": [],
} }
for label in data: for label in data:
boxes = data[label] boxes = data[label]
for box in boxes: for i, box in enumerate(boxes):
result['boxes'].append({ result['create']['boxes'].append({
"label_id": label, "label_id": label,
"frame": box[0], "frame": box[0],
"xtl": box[1], "xtl": box[1],
@ -128,12 +136,12 @@ def convert_to_cvat_format(data):
"z_order": 0, "z_order": 0,
"group_id": 0, "group_id": 0,
"occluded": False, "occluded": False,
"attributes": [] "attributes": [],
"client_id": i,
}) })
return result return result
def create_thread(id, labels_mapping): def create_thread(id, labels_mapping):
try: try:
TRESHOLD = 0.5 TRESHOLD = 0.5

@ -11,4 +11,4 @@ six==1.11.0
wrapt==1.10.11 wrapt==1.10.11
django-extensions==2.0.6 django-extensions==2.0.6
Werkzeug==0.14.1 Werkzeug==0.14.1
snakeviz==0.4.2 snakeviz==0.4.2

@ -50,6 +50,9 @@ module.exports = {
'showOverlay': true, 'showOverlay': true,
'confirm': true, 'confirm': true,
'dumpAnnotationRequest': true, 'dumpAnnotationRequest': true,
'createExportContainer': true,
'ExportType': true,
'getExportTargetContainer': true,
// from shapeCollection.js // from shapeCollection.js
'ShapeCollectionModel': true, 'ShapeCollectionModel': true,
'ShapeCollectionController': true, 'ShapeCollectionController': true,
@ -84,8 +87,6 @@ module.exports = {
'SELECT_POINT_STROKE_WIDTH': true, 'SELECT_POINT_STROKE_WIDTH': true,
// from mousetrap.js // from mousetrap.js
'Mousetrap': true, 'Mousetrap': true,
// from md5.js
'md5': true,
// from platform.js // from platform.js
'platform': true, 'platform': true,
// from player.js // from player.js

Loading…
Cancel
Save