Skip to content

Commit

Permalink
Redesigned AttributeSpec model + special migration for text field.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita Manovich committed Feb 6, 2019
1 parent e132c2b commit dff3b51
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 69 deletions.
37 changes: 26 additions & 11 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "CVAT Server",
"name": "Server",
"type": "python",
"request": "launch",
"stopOnEntry": false,
Expand All @@ -22,7 +22,7 @@
"cwd": "${workspaceFolder}"
},
{
"name": "CVAT Client",
"name": "Client",
"type": "chrome",
"request": "launch",
"url": "http://localhost:7000/",
Expand All @@ -35,7 +35,7 @@
}
},
{
"name": "CVAT RQ - default",
"name": "RQ - default",
"type": "python",
"request": "launch",
"stopOnEntry": false,
Expand All @@ -53,7 +53,7 @@
"env": {}
},
{
"name": "CVAT RQ - low",
"name": "RQ - low",
"type": "python",
"request": "launch",
"debugStdLib": true,
Expand All @@ -71,7 +71,7 @@
"env": {}
},
{
"name": "CVAT git",
"name": "git",
"type": "python",
"request": "launch",
"debugStdLib": true,
Expand All @@ -85,16 +85,31 @@
"cwd": "${workspaceFolder}",
"env": {}
},
{
"name": "migrate",
"type": "python",
"request": "launch",
"debugStdLib": true,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"migrate"
],
"django": true,
"cwd": "${workspaceFolder}",
"env": {}
},
],
"compounds": [
{
"name": "CVAT Debugging",
"name": "Debugging",
"configurations": [
"CVAT Client",
"CVAT Server",
"CVAT RQ - default",
"CVAT RQ - low",
"CVAT git",
"Client",
"Server",
"RQ - default",
"RQ - low",
"git",
]
}
]
Expand Down
106 changes: 106 additions & 0 deletions cvat/apps/engine/migrations/0027_auto_20190206_1318.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Generated by Django 2.1.5 on 2019-02-06 10:18

import cvat.apps.engine.models
from django.db import migrations, models
import re
import csv
from io import StringIO

def parse_attribute(value):
match = re.match(r'^([~@])(\w+)=(\w+):(.+)?$', value)
if match:
prefix = match.group(1)
input_type = match.group(2)
name = match.group(3)
if match.group(4):
values = list(csv.reader(StringIO(match.group(4)),
quotechar="'"))[0]
else:
values = []

return {'prefix':prefix, 'type':input_type, 'name':name, 'values':values}
else:
return None

def split_text_attribute(apps, schema_editor):
AttributeSpec = apps.get_model('engine', 'AttributeSpec')
for attribute in AttributeSpec.objects.all():
spec = parse_attribute(attribute.text)
if spec:
attribute.mutable = (spec['prefix'] == '~')
attribute.input_type = spec['type']
attribute.name = spec['name']
attribute.default_value = spec['values'][0]
attribute.values = '\n'.join(spec['values'])
attribute.save()

def join_text_attribute(apps, schema_editor):
AttributeSpec = apps.get_model('engine', 'AttributeSpec')
for attribute in AttributeSpec.objects.all():
attribute.text = ""
if attribute.mutable:
attribute.text += "~"
else:
attribute.text += "@"

attribute.text += attribute.input_type
attribute.text += "=" + attribute.name + ":"
attribute.text += ",".join(attribute.values.split('\n'))
attribute.save()

class Migration(migrations.Migration):

dependencies = [
('engine', '0026_auto_20190206_0025'),
]

operations = [
migrations.RemoveField(
model_name='task',
name='source',
),
migrations.AddField(
model_name='attributespec',
name='default_value',
field=models.CharField(default='', max_length=128),
preserve_default=False,
),
migrations.AddField(
model_name='attributespec',
name='input_type',
field=models.CharField(choices=[('CHECKBOX', 'checkbox'), ('RADIO', 'radio'), ('NUMBER', 'number'), ('TEXT', 'text'), ('SELECT', 'select')], default='select', max_length=16),
preserve_default=False,
),
migrations.AddField(
model_name='attributespec',
name='mutable',
field=models.BooleanField(default=True),
preserve_default=False,
),
migrations.AddField(
model_name='attributespec',
name='name',
field=models.CharField(default='test', max_length=64),
preserve_default=False,
),
migrations.AddField(
model_name='attributespec',
name='values',
field=models.CharField(default='', max_length=4096),
preserve_default=False,
),
migrations.AlterField(
model_name='job',
name='status',
field=models.CharField(choices=[('ANNOTATION', 'annotation'), ('VALIDATION', 'validation'), ('COMPLETED', 'completed')], default=cvat.apps.engine.models.StatusChoice('annotation'), max_length=32),
),
migrations.RunPython(split_text_attribute, join_text_attribute),
migrations.RemoveField(
model_name='attributespec',
name='text',
),
migrations.AlterUniqueTogether(
name='attributespec',
unique_together={('label', 'name')},
),
]
73 changes: 22 additions & 51 deletions cvat/apps/engine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
from django.contrib.auth.models import User
from django.core.files.storage import FileSystemStorage

from io import StringIO
from enum import Enum

import shlex
import csv
import re
import os
import sys


class StatusChoice(str, Enum):
Expand All @@ -27,6 +24,18 @@ class StatusChoice(str, Enum):
def choices(self):
return tuple((x.name, x.value) for x in self)

class AttributeType(str, Enum):
CHECKBOX = 'checkbox'
RADIO = 'radio'
NUMBER = 'number'
TEXT = 'text'
SELECT = 'select'

@classmethod
def choices(self):
return tuple((x.name, x.value) for x in self)


class SafeCharField(models.CharField):
def get_prep_value(self, value):
value = super().get_prep_value(value)
Expand All @@ -50,8 +59,6 @@ class Task(models.Model):
z_order = models.BooleanField(default=False)
flipped = models.BooleanField(default=False)
image_quality = models.PositiveSmallIntegerField()
# FIXME: remote source field
source = SafeCharField(max_length=256, default="unknown")
status = models.CharField(max_length=32, choices=StatusChoice.choices(),
default=StatusChoice.ANNOTATION)

Expand Down Expand Up @@ -136,7 +143,8 @@ class Meta:
class Job(models.Model):
segment = models.ForeignKey(Segment, on_delete=models.CASCADE)
assignee = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
status = models.CharField(max_length=32, default=StatusChoice.ANNOTATION)
status = models.CharField(max_length=32, choices=StatusChoice.choices(),
default=StatusChoice.ANNOTATION)
max_shape_id = models.BigIntegerField(default=-1)

class Meta:
Expand All @@ -153,58 +161,21 @@ class Meta:
default_permissions = ()
unique_together = ('task', 'name')

# FIXME: need to remote text and add (name, type, permanent,
# default_value, values)

class AttributeSpec(models.Model):
label = models.ForeignKey(Label, on_delete=models.CASCADE)
text = models.CharField(max_length=1024)
name = models.CharField(max_length=64)
mutable = models.BooleanField()
input_type = models.CharField(max_length=16, choices=AttributeType.choices())
default_value = models.CharField(max_length=128)
values = models.CharField(max_length=4096)

class Meta:
default_permissions = ()
# FIXME: unique_together = ('label', 'name')

@classmethod
def parse(cls, value):
match = re.match(r'^([~@])(\w+)=(\w+):(.+)?$', value)
if match:
prefix = match.group(1)
type = match.group(2)
name = match.group(3)
if match.group(4):
values = list(csv.reader(StringIO(match.group(4)),
quotechar="'"))[0]
else:
values = []

return {'prefix':prefix, 'type':type, 'name':name, 'values':values}
else:
return None

def get_attribute(self):
return self.parse(self.text)

def is_mutable(self):
attr = self.get_attribute()
return attr['prefix'] == '~'

def get_type(self):
attr = self.get_attribute()
return attr['type']

def get_name(self):
attr = self.get_attribute()
return attr['name']

def get_default_value(self):
attr = self.get_attribute()
return attr['values'][0]

def get_values(self):
attr = self.get_attribute()
return attr['values']
unique_together = ('label', 'name')

def __str__(self):
return self.get_attribute()['name']
return self.name


class AttributeVal(models.Model):
Expand Down
16 changes: 9 additions & 7 deletions cvat/apps/engine/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@
class AttributeSerializer(serializers.ModelSerializer):
class Meta:
model = AttributeSpec
fields = ('id', 'text')
fields = ('id', 'name', 'mutable', 'input_type', 'default_value',
'values')

def validate_text(self, value):
attr = AttributeSpec.parse(value)
if attr is None:
message = "{} attribute value isn't correct".format(value)
raise serializers.ValidationError(message)
def to_internal_value(self, data):
data['values'] = '\n'.join(data['values'])

def to_representation(self, instance):
attribute = super().to_representation(instance)
attribute['values'] = attribute['values'].split('\n')
return attribute

return value

class LabelSerializer(serializers.ModelSerializer):
attributes = AttributeSerializer(many=True, source='attributespec_set',
Expand Down

0 comments on commit dff3b51

Please sign in to comment.