Bin
2025-12-17 dcf780a91c16b6be28635b6e2e0e702060ee19f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
"""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<br>'
        '`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)<br>'
        '`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<br>'
        '`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)