"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license.
"""
from core.label_config import replace_task_data_undefined_with_config_field
from core.utils.common import load_func
from data_export.models import DataExport
from django.conf import settings
from fsm.serializer_fields import FSMStateField
from label_studio_sdk._extensions.label_studio_tools.core.label_config import is_video_object_tracking
from label_studio_sdk._extensions.label_studio_tools.postprocessing.video import extract_key_frames
from ml.mixins import InteractiveMixin
from rest_flex_fields import FlexFieldsModelSerializer
from rest_framework import serializers
from tasks.models import Annotation, Task
from tasks.serializers import AnnotationDraftSerializer, PredictionSerializer
from users.models import User
from users.serializers import UserSimpleSerializer
from .models import ConvertedFormat, Export
class CompletedBySerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'first_name', 'last_name']
class AnnotationSerializer(FlexFieldsModelSerializer):
completed_by = serializers.PrimaryKeyRelatedField(read_only=True)
result = serializers.SerializerMethodField()
state = FSMStateField(read_only=True) # FSM state for annotations
class Meta:
model = Annotation
fields = '__all__'
expandable_fields = {'completed_by': (CompletedBySerializer,)}
def to_representation(self, instance):
"""Override to conditionally exclude FSM state field when feature flags are disabled."""
from core.current_request import CurrentContext
from core.feature_flags import flag_set
ret = super().to_representation(instance)
# Remove state field from output if either feature flag is disabled
user = CurrentContext.get_user()
if not (
flag_set('fflag_feat_fit_568_finite_state_management', user=user)
and flag_set('fflag_feat_fit_710_fsm_state_fields', user=user)
):
ret.pop('state', None)
return ret
def get_result(self, obj):
# run frames extraction on param, result and result type
if (
obj.result
and self.context.get('interpolate_key_frames', False)
and is_video_object_tracking(parsed_config=obj.project.get_parsed_config())
):
return extract_key_frames(obj.result)
return obj.result
class BaseExportDataSerializer(FlexFieldsModelSerializer):
annotations = AnnotationSerializer(many=True, read_only=True)
file_upload = serializers.ReadOnlyField(source='file_upload_name')
drafts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
predictions = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
state = FSMStateField(read_only=True) # FSM state for tasks
# resolve $undefined$ key in task data, if any
def to_representation(self, task):
from core.current_request import CurrentContext
from core.feature_flags import flag_set
# avoid long project initializations
project = getattr(self, '_project', None)
if project is None:
project = task.project
setattr(self, '_project', project)
data = task.data
# add interpolate_key_frames param to annotations serializer
if 'annotations' in self.fields:
self.fields['annotations'].context['interpolate_key_frames'] = self.context.get(
'interpolate_key_frames', False
)
replace_task_data_undefined_with_config_field(data, project)
ret = super().to_representation(task)
# Remove state field from output if either feature flag is disabled
user = CurrentContext.get_user()
if not (
flag_set('fflag_feat_fit_568_finite_state_management', user=user)
and flag_set('fflag_feat_fit_710_fsm_state_fields', user=user)
):
ret.pop('state', None)
return ret
class Meta:
model = Task
exclude = ('overlap', 'is_labeled', 'precomputed_agreement')
expandable_fields = {
'drafts': (AnnotationDraftSerializer, {'many': True}),
'predictions': (PredictionSerializer, {'many': True}),
'annotations': (AnnotationSerializer, {'many': True}),
}
class ConvertedFormatSerializer(serializers.ModelSerializer):
class Meta:
model = ConvertedFormat
fields = ['id', 'status', 'export_type', 'traceback']
def to_representation(self, instance):
from django.conf import settings
data = super().to_representation(instance)
if not getattr(settings, 'SHOW_TRACEBACK_FOR_EXPORT_CONVERTER', True):
# Remove traceback field from output if setting is disabled
data.pop('traceback', None)
return data
class ExportSerializer(serializers.ModelSerializer):
class Meta:
model = Export
read_only = [
'id',
'created_by',
'created_at',
'finished_at',
'status',
'md5',
'counters',
'converted_formats',
]
fields = ['title'] + read_only
created_by = UserSimpleSerializer(required=False)
converted_formats = ConvertedFormatSerializer(many=True, required=False)
ONLY_OR_EXCLUDE_CHOICE = [
2 * ['only'],
2 * ['exclude'],
2 * [None],
]
class TaskFilterOptionsSerializer(serializers.Serializer):
view = serializers.IntegerField(
required=False, help_text='Apply filters from the view ID (a tab from the Data Manager)'
)
skipped = serializers.ChoiceField(
choices=ONLY_OR_EXCLUDE_CHOICE,
allow_null=True,
required=False,
help_text='`only` - include all tasks with skipped annotations
'
'`exclude` - exclude all tasks with skipped annotations',
)
finished = serializers.ChoiceField(
choices=ONLY_OR_EXCLUDE_CHOICE,
allow_null=True,
required=False,
help_text='`only` - include all finished tasks (is_labeled = true)
'
'`exclude` - exclude all finished tasks',
)
annotated = serializers.ChoiceField(
choices=ONLY_OR_EXCLUDE_CHOICE,
allow_null=True,
required=False,
help_text='`only` - include all tasks with at least one not skipped annotation
'
'`exclude` - exclude all tasks with at least one not skipped annotation',
)
only_with_annotations = serializers.BooleanField(default=False, required=False, help_text='')
class AnnotationFilterOptionsSerializer(serializers.Serializer):
usual = serializers.BooleanField(
allow_null=True, required=False, default=True, help_text='Include not skipped and not ground truth annotations'
)
ground_truth = serializers.BooleanField(
allow_null=True, required=False, help_text='Include ground truth annotations'
)
skipped = serializers.BooleanField(allow_null=True, required=False, help_text='Include skipped annotations')
class SerializationOptionsSerializer(serializers.Serializer):
class SerializationOption(serializers.Serializer):
only_id = serializers.BooleanField(
default=False, required=False, help_text='Include a full json body or IDs only'
)
drafts = SerializationOption(required=False, help_text='JSON dict with parameters')
predictions = SerializationOption(required=False, help_text='JSON dict with parameters')
include_annotation_history = serializers.BooleanField(
default=False, help_text='Include annotation history', required=False
)
annotations__completed_by = SerializationOption(required=False, help_text='JSON dict with parameters')
interpolate_key_frames = serializers.BooleanField(
default=settings.INTERPOLATE_KEY_FRAMES, help_text='Interpolate video key frames', required=False
)
class ExportConvertSerializer(serializers.Serializer):
export_type = serializers.CharField(help_text='Export file format.')
download_resources = serializers.BooleanField(help_text='Download resources in converter.', required=False)
def validate_export_type(self, value):
project = self.context.get('project')
export_formats = [f['name'] for f in DataExport.get_export_formats(project)]
if value not in export_formats:
raise serializers.ValidationError(f'{value} is not supported export format')
return value
class ExportCreateSerializer(ExportSerializer):
class Meta(ExportSerializer.Meta):
fields = ExportSerializer.Meta.fields + [
'task_filter_options',
'annotation_filter_options',
'serialization_options',
]
task_filter_options = TaskFilterOptionsSerializer(required=False, default=None)
annotation_filter_options = AnnotationFilterOptionsSerializer(required=False, default=None)
serialization_options = SerializationOptionsSerializer(required=False, default=None)
class ExportParamSerializer(serializers.Serializer):
interpolate_key_frames = serializers.BooleanField(
default=settings.INTERPOLATE_KEY_FRAMES, help_text='Interpolate video key frames.', required=False
)
download_resources = serializers.BooleanField(
default=settings.CONVERTER_DOWNLOAD_RESOURCES, help_text='Download resources in converter.', required=False
)
# deprecated param to delete
export_type = serializers.CharField(default='JSON', help_text='Export file format.', required=False)
exportType = serializers.CharField(help_text='Export file format.', required=False)
download_all_tasks = serializers.BooleanField(
default=False, help_text='Download all tasks or only finished.', required=False
)
class BaseExportDataSerializerForInteractive(InteractiveMixin, BaseExportDataSerializer):
pass
ExportDataSerializer = load_func(settings.EXPORT_DATA_SERIALIZER)