Skip to content

Commit

Permalink
fix: LEAP-1352: API for Bulk Annotation Configuration Validation (#6757)
Browse files Browse the repository at this point in the history
Co-authored-by: hlomzik <[email protected]>
Co-authored-by: MihajloHoma <[email protected]>
Co-authored-by: triklozoid <[email protected]>
  • Loading branch information
4 people authored Dec 13, 2024
1 parent a900be2 commit 769ced2
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 1 deletion.
83 changes: 83 additions & 0 deletions label_studio/projects/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@
import bleach
from constants import SAFE_HTML_ATTRIBUTES, SAFE_HTML_TAGS
from django.db.models import Q
from label_studio_sdk.label_interface import LabelInterface
from label_studio_sdk.label_interface.control_tags import (
BrushLabelsTag,
BrushTag,
ChoicesTag,
DateTimeTag,
EllipseLabelsTag,
EllipseTag,
HyperTextLabelsTag,
KeyPointLabelsTag,
KeyPointTag,
LabelsTag,
NumberTag,
ParagraphLabelsTag,
PolygonLabelsTag,
PolygonTag,
RatingTag,
RectangleLabelsTag,
RectangleTag,
TaxonomyTag,
TextAreaTag,
TimeSeriesLabelsTag,
VideoRectangleTag,
)
from projects.models import Project, ProjectImport, ProjectOnboarding, ProjectReimport, ProjectSummary
from rest_flex_fields import FlexFieldsModelSerializer
from rest_framework import serializers
Expand Down Expand Up @@ -66,6 +90,9 @@ class ProjectSerializer(FlexFieldsModelSerializer):
config_has_control_tags = SerializerMethodField(
default=None, read_only=True, help_text='Flag to detect is project ready for labeling'
)
config_suitable_for_bulk_annotation = serializers.SerializerMethodField(
default=None, read_only=True, help_text='Flag to detect is project ready for bulk annotation'
)
finished_task_number = serializers.IntegerField(default=None, read_only=True, help_text='Finished tasks')

queue_total = serializers.SerializerMethodField()
Expand All @@ -82,6 +109,61 @@ def user_id(self):
def get_config_has_control_tags(project):
return len(project.get_parsed_config()) > 0

@staticmethod
def get_config_suitable_for_bulk_annotation(project):
li = LabelInterface(project.label_config)

# List of tags that should not be present
disallowed_tags = [
LabelsTag,
BrushTag,
BrushLabelsTag,
EllipseTag,
EllipseLabelsTag,
KeyPointTag,
KeyPointLabelsTag,
PolygonTag,
PolygonLabelsTag,
RectangleTag,
RectangleLabelsTag,
HyperTextLabelsTag,
ParagraphLabelsTag,
TimeSeriesLabelsTag,
VideoRectangleTag,
]

# Return False if any disallowed tag is present
for tag_class in disallowed_tags:
if li.find_tags_by_class(tag_class):
return False

# Check perRegion/perItem for expanded list of tags, plus value="no" for Choices/Taxonomy
allowed_tags_for_checks = [ChoicesTag, TaxonomyTag, DateTimeTag, NumberTag, RatingTag, TextAreaTag]
for tag_class in allowed_tags_for_checks:
tags = li.find_tags_by_class(tag_class)
for tag in tags:
per_region = tag.attr.get('perRegion', 'false').lower() == 'true'
per_item = tag.attr.get('perItem', 'false').lower() == 'true'
if per_region or per_item:
return False
# For ChoicesTag and TaxonomyTag, the value attribute must not be set at all
if tag_class in [ChoicesTag, TaxonomyTag]:
if 'value' in tag.attr:
return False

# For TaxonomyTag, check labeling and apiUrl
taxonomy_tags = li.find_tags_by_class(TaxonomyTag)
for tag in taxonomy_tags:
labeling = tag.attr.get('labeling', 'false').lower() == 'true'
if labeling:
return False
api_url = tag.attr.get('apiUrl', None)
if api_url is not None:
return False

# If all checks pass, return True
return True

@staticmethod
def get_parsed_label_config(project):
return project.get_parsed_config()
Expand Down Expand Up @@ -156,6 +238,7 @@ class Meta:
'finished_task_number',
'queue_total',
'queue_done',
'config_suitable_for_bulk_annotation',
]

def validate_label_config(self, value):
Expand Down
71 changes: 70 additions & 1 deletion label_studio/tests/config_validation.tavern.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1435,4 +1435,73 @@ stages:
method: POST
url: '{django_live_url}/api/projects/{pk}/validate'
response:
status_code: 200
status_code: 200

---
test_name: check_config_suitable_for_bulk_annotation
strict: false
marks:
- usefixtures:
- django_live_url
stages:

- id: signup
type: ref

- name: create classification project
request:
data:
label_config: |
<View>
<Text name="text" value="$text"/>
<Choices name="sentiment" toName="text" choice="single">
<Choice value="Positive"/>
<Choice value="Neutral"/>
<Choice value="Negative"/>
</Choices>
</View>
method: POST
url: '{django_live_url}/api/projects'
response:
status_code: 201
save:
json:
classification_project_id: id

- name: check classification project property
request:
method: GET
url: '{django_live_url}/api/projects/{classification_project_id}'
response:
status_code: 200
json:
config_suitable_for_bulk_annotation: true

- name: create object detection project
request:
data:
label_config: |
<View>
<Image name="image" value="$image"/>
<RectangleLabels name="label" toName="image">
<Label value="Car"/>
<Label value="Tree"/>
<Label value="Person"/>
</RectangleLabels>
</View>
method: POST
url: '{django_live_url}/api/projects'
response:
status_code: 201
save:
json:
detection_project_id: id

- name: check object detection project property
request:
method: GET
url: '{django_live_url}/api/projects/{detection_project_id}'
response:
status_code: 200
json:
config_suitable_for_bulk_annotation: false

0 comments on commit 769ced2

Please sign in to comment.