2
2
3
3
import json
4
4
from pathlib import Path
5
- from typing import Callable , Dict , List
5
+ from typing import Dict , List , Callable
6
6
7
7
from kili .services .export .exceptions import NotCompatibleInputType , NotCompatibleOptions
8
- from kili .services .export .format .base import AbstractExporter
8
+ from kili .services .export .format .base import AbstractExporter , reverse_rotation_vertices
9
9
from kili .services .export .media .video import cut_video
10
10
from kili .services .types import Job
11
11
12
12
13
+ def _clean_json_response (asset : Dict ):
14
+ if "latestLabel" in asset and "jsonResponse" in asset ["latestLabel" ]:
15
+ if "ROTATION_JOB" in asset ["latestLabel" ]["jsonResponse" ]:
16
+ asset ["latestLabel" ]["jsonResponse" ].pop ("ROTATION_JOB" )
17
+ if "labels" in asset :
18
+ for label in asset ["labels" ]:
19
+ if "jsonResponse" in label and "ROTATION_JOB" in label ["jsonResponse" ]:
20
+ label ["jsonResponse" ].pop ("ROTATION_JOB" )
21
+ return asset
22
+
23
+
13
24
class KiliExporter (AbstractExporter ):
14
25
"""Common code for Kili exporters."""
15
26
@@ -96,10 +107,10 @@ def _cut_video_assets(self, assets: List[Dict]) -> List[Dict]:
96
107
def process_and_save (self , assets : List [Dict ], output_filename : Path ) -> None :
97
108
"""Extract formatted annotations from labels and save the json in the buckets."""
98
109
clean_assets = self .preprocess_assets (assets )
99
-
100
- if self . normalized_coordinates is False :
101
- clean_assets = [ self .convert_to_pixel_coords (asset ) for asset in clean_assets ]
102
-
110
+ if self . project [ "inputType" ] != "LLM_RLHF" :
111
+ for i , asset in enumerate ( clean_assets ) :
112
+ clean_assets [ i ] = self .convert_to_pixel_coords (asset )
113
+ clean_assets [ i ] = _clean_json_response ( asset )
103
114
return self ._save_assets_export (
104
115
clean_assets ,
105
116
output_filename ,
@@ -131,30 +142,38 @@ def _scale_label_vertices(self, label: Dict, asset: Dict) -> None:
131
142
else False
132
143
)
133
144
145
+ rotation_val = 0
146
+ if "ROTATION_JOB" in label ["jsonResponse" ]:
147
+ rotation_val = label ["jsonResponse" ]["ROTATION_JOB" ]["rotation" ]
148
+
149
+ normalized_vertices = self .normalized_coordinates is not False
150
+
134
151
if self .project ["inputType" ] == "PDF" :
135
152
self ._scale_json_response_vertices (
136
- label ["jsonResponse" ],
137
- asset ,
138
- is_label_rotated ,
139
- _scale_normalized_vertices_pdf_annotation ,
153
+ json_resp = label ["jsonResponse" ],
154
+ asset = asset ,
155
+ is_label_rotated = is_label_rotated ,
156
+ annotation_scaler = _scale_normalized_vertices_pdf_annotation ,
140
157
)
141
158
142
159
elif self .project ["inputType" ] == "IMAGE" :
143
160
self ._scale_json_response_vertices (
144
- label ["jsonResponse" ],
145
- asset ,
146
- is_label_rotated ,
147
- _scale_normalized_vertices_image_video_annotation ,
161
+ json_resp = label ["jsonResponse" ],
162
+ asset = asset ,
163
+ rotation = rotation_val ,
164
+ normalized_vertices = normalized_vertices ,
165
+ annotation_scaler = _scale_normalized_vertices_image_video_annotation ,
148
166
)
149
167
150
168
elif self .project ["inputType" ] == "VIDEO" :
151
169
for frame_resp in label ["jsonResponse" ].values ():
152
170
if frame_resp :
153
171
self ._scale_json_response_vertices (
154
- frame_resp ,
155
- asset ,
156
- is_label_rotated ,
157
- _scale_normalized_vertices_image_video_annotation ,
172
+ json_resp = frame_resp ,
173
+ asset = asset ,
174
+ rotation = rotation_val ,
175
+ normalized_vertices = normalized_vertices ,
176
+ annotation_scaler = _scale_normalized_vertices_image_video_annotation ,
158
177
)
159
178
160
179
else :
@@ -163,19 +182,15 @@ def _scale_label_vertices(self, label: Dict, asset: Dict) -> None:
163
182
" coordinates."
164
183
)
165
184
166
- def _scale_json_response_vertices (
167
- self ,
168
- json_resp : Dict ,
169
- asset : Dict ,
170
- is_label_rotated : bool ,
171
- annotation_scaler : Callable [[Dict , Dict , bool ], None ],
172
- ) -> None :
185
+ def _scale_json_response_vertices (self , asset : Dict , json_resp : Dict , annotation_scaler : Callable , ** kwargs ) -> None :
186
+ if not callable (annotation_scaler ):
187
+ return
173
188
for job_name in json_resp :
174
189
if self ._can_scale_vertices_for_job_name (job_name ) and json_resp .get (job_name , {}).get (
175
190
"annotations"
176
191
):
177
192
for ann in json_resp [job_name ]["annotations" ]:
178
- annotation_scaler (ann , asset , is_label_rotated )
193
+ annotation_scaler (ann , asset , ** kwargs )
179
194
180
195
def _can_scale_vertices_for_job_name (self , job_name : str ) -> bool :
181
196
return (
@@ -211,23 +226,23 @@ def _scale_all_vertices(object_, width: int, height: int):
211
226
return object_
212
227
213
228
214
- def _scale_normalized_vertices_pdf_annotation (
215
- annotation : Dict , asset : Dict , is_label_rotated : bool = False
216
- ) -> None :
229
+ def _scale_normalized_vertices_pdf_annotation (annotation : Dict , asset : Dict , ** kwargs ) -> None :
217
230
"""Scale normalized vertices of a PDF annotation.
218
231
219
232
PDF annotations are different from image annotations because the asset width/height can vary.
220
233
221
234
PDF only have BBox detection, so we only scale the boundingPoly and polys keys.
222
235
"""
236
+ is_label_rotated = kwargs .get ("is_label_rotated" , False )
237
+
223
238
if is_label_rotated :
224
239
raise NotCompatibleOptions ("PDF labels cannot be rotated" )
225
240
226
241
if "annotations" in annotation :
227
242
# pdf annotations have two layers of "annotations"
228
243
# https://docs.kili-technology.com/reference/export-object-entity-detection-and-relation#ner-in-pdfs
229
244
for ann in annotation ["annotations" ]:
230
- _scale_normalized_vertices_pdf_annotation (ann , asset )
245
+ _scale_normalized_vertices_pdf_annotation (ann , asset , ** kwargs )
231
246
232
247
# an annotation has three keys:
233
248
# - pageNumberArray: list of page numbers
@@ -264,32 +279,47 @@ def _scale_normalized_vertices_pdf_annotation(
264
279
]
265
280
266
281
267
- def _scale_normalized_vertices_image_video_annotation (
268
- annotation : Dict , asset : Dict , is_label_rotated : bool
269
- ) -> None :
282
+ def _scale_normalized_vertices_image_video_annotation (annotation : Dict , asset : Dict , ** kwargs ) -> None :
270
283
"""Scale normalized vertices of an image/video object detection annotation."""
271
- if "resolution" not in asset or asset ["resolution" ] is None :
284
+ rotation = kwargs .get ("rotation" , 0 )
285
+ normalized_vertices = kwargs .get ("normalized_vertices" , True )
286
+
287
+ if not normalized_vertices and ("resolution" not in asset or asset ["resolution" ] is None ):
272
288
raise NotCompatibleOptions (
273
289
"Image and video labels export with absolute coordinates require `resolution` in the"
274
290
" asset. Please use `kili.update_properties_in_assets(resolution_array=...)` to update"
275
291
" the resolution of your asset.`"
276
292
)
277
293
278
- width = asset ["resolution" ]["width" ] if not is_label_rotated else asset [ "resolution" ][ "height" ]
279
- height = asset ["resolution" ]["height" ] if not is_label_rotated else asset [ "resolution" ][ "width" ]
294
+ width = asset ["resolution" ]["width" ] if "resolution" in asset else 0
295
+ height = asset ["resolution" ]["height" ] if "resolution" in asset else 0
280
296
281
297
# bbox, segmentation, polygons
282
- if "boundingPoly" in annotation :
298
+ if "boundingPoly" in annotation and normalized_vertices :
283
299
annotation ["boundingPoly" ] = [
284
300
{
285
- ** norm_vertices_dict , # keep the original normalizedVertices
286
- "vertices" : _scale_all_vertices (
287
- norm_vertices_dict ["normalizedVertices" ], width = width , height = height
301
+ "normalizedVertices" : reverse_rotation_vertices (
302
+ norm_vertices_dict ["normalizedVertices" ], rotation
288
303
),
289
304
}
290
305
for norm_vertices_dict in annotation ["boundingPoly" ]
291
306
]
307
+ return
292
308
309
+ if "boundingPoly" in annotation and not normalized_vertices :
310
+ annotation ["boundingPoly" ] = [
311
+ {
312
+ "normalizedVertices" : reverse_rotation_vertices (
313
+ norm_vertices_dict ["normalizedVertices" ], rotation
314
+ ),
315
+ "vertices" : _scale_all_vertices (
316
+ reverse_rotation_vertices (norm_vertices_dict ["normalizedVertices" ], rotation ),
317
+ width = width ,
318
+ height = height ,
319
+ ),
320
+ }
321
+ for norm_vertices_dict in annotation ["boundingPoly" ]
322
+ ]
293
323
# point jobs
294
324
if "point" in annotation :
295
325
annotation ["pointPixels" ] = _scale_all_vertices (
0 commit comments