Add an extra field into meta section of a dump file (#149)

Fix #56
* Added extra field into meta section of a dump file.
* Add SafeCharField.
main
Nikita Manovich 7 years ago committed by GitHub
parent b353d83805
commit 89234496ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,10 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Shortcuts for outside/keyframe properties - Shortcuts for outside/keyframe properties
- OpenVINO for accelerated model inference - OpenVINO for accelerated model inference
- Tensorflow annotation now works without CUDA. It can use CPU only. OpenVINO and CUDA are supported optionally. - Tensorflow annotation now works without CUDA. It can use CPU only. OpenVINO and CUDA are supported optionally.
- Incremental saving, client ID field for all annotated objects.
### Changed ### Changed
- Polyshape editing method has been improved. You can redraw part of shape instead of points cloning. - Polyshape editing method has been improved. You can redraw part of shape instead of points cloning.
- Unified shortcut (Esc) for close any mode instead of different shortcuts (Alt+N, Alt+G, Alt+M etc.). - Unified shortcut (Esc) for close any mode instead of different shortcuts (Alt+N, Alt+G, Alt+M etc.).
- Dump file contains information about data source (e.g. video name, archive name, ...)
### Fixed ### Fixed
- Performance bottleneck has been fixed during you create new objects (draw, copy, merge etc). - Performance bottleneck has been fixed during you create new objects (draw, copy, merge etc).

@ -1437,7 +1437,7 @@ def _dump(tid, data_format, scheme, host):
db_task = models.Task.objects.select_for_update().get(id=tid) db_task = models.Task.objects.select_for_update().get(id=tid)
annotation = _AnnotationForTask(db_task) annotation = _AnnotationForTask(db_task)
annotation.init_from_db() annotation.init_from_db()
annotation.dump(data_format, db_task, scheme, host) annotation.dump(data_format, scheme, host)
def _calc_box_area(box): def _calc_box_area(box):
return (box.xbr - box.xtl) * (box.ybr - box.ytl) return (box.xbr - box.xtl) * (box.ybr - box.ytl)
@ -1816,7 +1816,7 @@ class _AnnotationForTask(_Annotation):
# We don't have old boxes on the frame. Let's add all new ones. # We don't have old boxes on the frame. Let's add all new ones.
self.boxes.extend(int_boxes_by_frame[frame]) self.boxes.extend(int_boxes_by_frame[frame])
def dump(self, data_format, db_task, scheme, host): def dump(self, data_format, scheme, host):
def _flip_box(box, im_w, im_h): def _flip_box(box, im_w, im_h):
box.xbr, box.xtl = im_w - box.xtl, im_w - box.xbr box.xbr, box.xtl = im_w - box.xtl, im_w - box.xbr
box.ybr, box.ytl = im_h - box.ytl, im_h - box.ybr box.ybr, box.ytl = im_h - box.ytl, im_h - box.ybr
@ -1836,6 +1836,7 @@ class _AnnotationForTask(_Annotation):
shape.points = ' '.join(['{},{}'.format(point['x'], point['y']) for point in points]) shape.points = ' '.join(['{},{}'.format(point['x'], point['y']) for point in points])
db_task = self.db_task
db_segments = db_task.segment_set.all().prefetch_related('job_set') db_segments = db_task.segment_set.all().prefetch_related('job_set')
db_labels = db_task.label_set.all().prefetch_related('attributespec_set') db_labels = db_task.label_set.all().prefetch_related('attributespec_set')
im_meta_data = get_image_meta_cache(db_task) im_meta_data = get_image_meta_cache(db_task)
@ -1851,6 +1852,7 @@ class _AnnotationForTask(_Annotation):
("flipped", str(db_task.flipped)), ("flipped", str(db_task.flipped)),
("created", str(timezone.localtime(db_task.created_date))), ("created", str(timezone.localtime(db_task.created_date))),
("updated", str(timezone.localtime(db_task.updated_date))), ("updated", str(timezone.localtime(db_task.updated_date))),
("source", db_task.source),
("labels", [ ("labels", [
("label", OrderedDict([ ("label", OrderedDict([
@ -1878,19 +1880,19 @@ class _AnnotationForTask(_Annotation):
("dumped", str(timezone.localtime(timezone.now()))) ("dumped", str(timezone.localtime(timezone.now())))
]) ])
if self.db_task.mode == "interpolation": if db_task.mode == "interpolation":
meta["task"]["original_size"] = OrderedDict([ meta["task"]["original_size"] = OrderedDict([
("width", str(im_meta_data["original_size"][0]["width"])), ("width", str(im_meta_data["original_size"][0]["width"])),
("height", str(im_meta_data["original_size"][0]["height"])) ("height", str(im_meta_data["original_size"][0]["height"]))
]) ])
dump_path = self.db_task.get_dump_path() dump_path = db_task.get_dump_path()
with open(dump_path, "w") as dump_file: with open(dump_path, "w") as dump_file:
dumper = _XmlAnnotationWriter(dump_file) dumper = _XmlAnnotationWriter(dump_file)
dumper.open_root() dumper.open_root()
dumper.add_meta(meta) dumper.add_meta(meta)
if self.db_task.mode == "annotation": if db_task.mode == "annotation":
shapes = {} shapes = {}
shapes["boxes"] = {} shapes["boxes"] = {}
shapes["polygons"] = {} shapes["polygons"] = {}
@ -1925,7 +1927,7 @@ class _AnnotationForTask(_Annotation):
list(shapes["polylines"].keys()) + list(shapes["polylines"].keys()) +
list(shapes["points"].keys()))): list(shapes["points"].keys()))):
link = get_frame_path(self.db_task.id, frame) link = get_frame_path(db_task.id, frame)
path = os.readlink(link) path = os.readlink(link)
rpath = path.split(os.path.sep) rpath = path.split(os.path.sep)

@ -0,0 +1,74 @@
# Generated by Django 2.0.9 on 2018-10-24 10:50
import cvat.apps.engine.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('engine', '0010_auto_20181011_1517'),
]
operations = [
migrations.AddField(
model_name='task',
name='source',
field=cvat.apps.engine.models.SafeCharField(default='unknown', max_length=256),
),
migrations.AlterField(
model_name='label',
name='name',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='labeledboxattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='labeledpointsattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='labeledpolygonattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='labeledpolylineattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='objectpathattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='task',
name='name',
field=cvat.apps.engine.models.SafeCharField(max_length=256),
),
migrations.AlterField(
model_name='trackedboxattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='trackedpointsattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='trackedpolygonattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
migrations.AlterField(
model_name='trackedpolylineattributeval',
name='value',
field=cvat.apps.engine.models.SafeCharField(max_length=64),
),
]

@ -14,9 +14,15 @@ from io import StringIO
import re import re
import os import os
class SafeCharField(models.CharField):
def get_prep_value(self, value):
value = super().get_prep_value(value)
if value:
return value[:self.max_length]
return value
class Task(models.Model): class Task(models.Model):
name = models.CharField(max_length=256) name = SafeCharField(max_length=256)
size = models.PositiveIntegerField() size = models.PositiveIntegerField()
path = models.CharField(max_length=256) path = models.CharField(max_length=256)
mode = models.CharField(max_length=32) mode = models.CharField(max_length=32)
@ -28,6 +34,7 @@ class Task(models.Model):
overlap = models.PositiveIntegerField(default=0) overlap = models.PositiveIntegerField(default=0)
z_order = models.BooleanField(default=False) z_order = models.BooleanField(default=False)
flipped = models.BooleanField(default=False) flipped = models.BooleanField(default=False)
source = SafeCharField(max_length=256, default="unknown")
# Extend default permission model # Extend default permission model
class Meta: class Meta:
@ -78,7 +85,7 @@ class Job(models.Model):
class Label(models.Model): class Label(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE) task = models.ForeignKey(Task, on_delete=models.CASCADE)
name = models.CharField(max_length=64) name = SafeCharField(max_length=64)
def __str__(self): def __str__(self):
return self.name return self.name
@ -130,7 +137,7 @@ 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) 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 = SafeCharField(max_length=64)
class Meta: class Meta:
abstract = True abstract = True

@ -498,6 +498,8 @@ def _find_and_unpack_archive(upload_dir):
else: else:
raise Exception('Type defined as archive, but archives were not found.') raise Exception('Type defined as archive, but archives were not found.')
return archive
''' '''
Search a video in upload dir and split it by frames. Copy frames to target dirs Search a video in upload dir and split it by frames. Copy frames to target dirs
@ -525,6 +527,8 @@ def _find_and_extract_video(upload_dir, output_dir, db_task, compress_quality, f
else: else:
raise Exception("Video files were not found") raise Exception("Video files were not found")
return video
''' '''
Recursive search for all images in upload dir and compress it to RGB jpg with specified quality. Create symlinks for them. Recursive search for all images in upload dir and compress it to RGB jpg with specified quality. Create symlinks for them.
@ -565,11 +569,14 @@ def _find_and_compress_images(upload_dir, output_dir, db_task, compress_quality,
else: else:
raise Exception("Image files were not found") raise Exception("Image files were not found")
return filenames
def _save_task_to_db(db_task, task_params): def _save_task_to_db(db_task, task_params):
db_task.overlap = min(db_task.size, task_params['overlap']) db_task.overlap = min(db_task.size, task_params['overlap'])
db_task.mode = task_params['mode'] db_task.mode = task_params['mode']
db_task.z_order = task_params['z_order'] db_task.z_order = task_params['z_order']
db_task.flipped = task_params['flip'] db_task.flipped = task_params['flip']
db_task.source = task_params['data']
segment_step = task_params['segment'] - db_task.overlap segment_step = task_params['segment'] - db_task.overlap
for x in range(0, db_task.size, segment_step): for x in range(0, db_task.size, segment_step):
@ -638,10 +645,11 @@ def _create_thread(tid, params):
job.save_meta() job.save_meta()
_copy_data_from_share(share_files_mapping, share_dirs_mapping) _copy_data_from_share(share_files_mapping, share_dirs_mapping)
archive = None
if counters['archive']: if counters['archive']:
job.meta['status'] = 'Archive is being unpacked..' job.meta['status'] = 'Archive is being unpacked..'
job.save_meta() job.save_meta()
_find_and_unpack_archive(upload_dir) archive = _find_and_unpack_archive(upload_dir)
# Define task mode and other parameters # Define task mode and other parameters
task_params = { task_params = {
@ -657,9 +665,18 @@ def _create_thread(tid, params):
slogger.glob.info("Task #{} parameters: {}".format(tid, task_params)) slogger.glob.info("Task #{} parameters: {}".format(tid, task_params))
if task_params['mode'] == 'interpolation': if task_params['mode'] == 'interpolation':
_find_and_extract_video(upload_dir, output_dir, db_task, task_params['compress'], task_params['flip'], job) video = _find_and_extract_video(upload_dir, output_dir, db_task,
task_params['compress'], task_params['flip'], job)
task_params['data'] = os.path.relpath(video, upload_dir)
else: else:
_find_and_compress_images(upload_dir, output_dir, db_task, task_params['compress'], task_params['flip'], job) files =_find_and_compress_images(upload_dir, output_dir, db_task,
task_params['compress'], task_params['flip'], job)
if archive:
task_params['data'] = os.path.relpath(archive, upload_dir)
else:
task_params['data'] = '{} images: {}, ...'.format(len(files),
", ".join([os.path.relpath(x, upload_dir) for x in files[0:2]]))
slogger.glob.info("Founded frames {} for task #{}".format(db_task.size, tid)) slogger.glob.info("Founded frames {} for task #{}".format(db_task.size, tid))
job.meta['status'] = 'Task is being saved in database' job.meta['status'] = 'Task is being saved in database'

Loading…
Cancel
Save