Skip to content

Commit 4108cb0

Browse files
author
Sihem Tchabi
committed
fix(LAB-3447): correct calculation of vertices in kili format
1 parent a66486a commit 4108cb0

File tree

2 files changed

+70
-35
lines changed

2 files changed

+70
-35
lines changed

src/kili/services/export/format/kili/__init__.py

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,22 @@
55
from typing import Callable, Dict, List
66

77
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
99
from kili.services.export.media.video import cut_video
1010
from kili.services.types import Job
1111

1212

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+
1324
class KiliExporter(AbstractExporter):
1425
"""Common code for Kili exporters."""
1526

@@ -96,10 +107,10 @@ def _cut_video_assets(self, assets: List[Dict]) -> List[Dict]:
96107
def process_and_save(self, assets: List[Dict], output_filename: Path) -> None:
97108
"""Extract formatted annotations from labels and save the json in the buckets."""
98109
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)
103114
return self._save_assets_export(
104115
clean_assets,
105116
output_filename,
@@ -131,30 +142,38 @@ def _scale_label_vertices(self, label: Dict, asset: Dict) -> None:
131142
else False
132143
)
133144

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+
134151
if self.project["inputType"] == "PDF":
135152
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,
140157
)
141158

142159
elif self.project["inputType"] == "IMAGE":
143160
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,
148166
)
149167

150168
elif self.project["inputType"] == "VIDEO":
151169
for frame_resp in label["jsonResponse"].values():
152170
if frame_resp:
153171
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,
158177
)
159178

160179
else:
@@ -164,18 +183,16 @@ def _scale_label_vertices(self, label: Dict, asset: Dict) -> None:
164183
)
165184

166185
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],
186+
self, asset: Dict, json_resp: Dict, annotation_scaler: Callable, **kwargs
172187
) -> None:
188+
if not callable(annotation_scaler):
189+
return
173190
for job_name in json_resp:
174191
if self._can_scale_vertices_for_job_name(job_name) and json_resp.get(job_name, {}).get(
175192
"annotations"
176193
):
177194
for ann in json_resp[job_name]["annotations"]:
178-
annotation_scaler(ann, asset, is_label_rotated)
195+
annotation_scaler(ann, asset, **kwargs)
179196

180197
def _can_scale_vertices_for_job_name(self, job_name: str) -> bool:
181198
return (
@@ -211,23 +228,23 @@ def _scale_all_vertices(object_, width: int, height: int):
211228
return object_
212229

213230

214-
def _scale_normalized_vertices_pdf_annotation(
215-
annotation: Dict, asset: Dict, is_label_rotated: bool = False
216-
) -> None:
231+
def _scale_normalized_vertices_pdf_annotation(annotation: Dict, asset: Dict, **kwargs) -> None:
217232
"""Scale normalized vertices of a PDF annotation.
218233
219234
PDF annotations are different from image annotations because the asset width/height can vary.
220235
221236
PDF only have BBox detection, so we only scale the boundingPoly and polys keys.
222237
"""
238+
is_label_rotated = kwargs.get("is_label_rotated", False)
239+
223240
if is_label_rotated:
224241
raise NotCompatibleOptions("PDF labels cannot be rotated")
225242

226243
if "annotations" in annotation:
227244
# pdf annotations have two layers of "annotations"
228245
# https://docs.kili-technology.com/reference/export-object-entity-detection-and-relation#ner-in-pdfs
229246
for ann in annotation["annotations"]:
230-
_scale_normalized_vertices_pdf_annotation(ann, asset)
247+
_scale_normalized_vertices_pdf_annotation(ann, asset, **kwargs)
231248

232249
# an annotation has three keys:
233250
# - pageNumberArray: list of page numbers
@@ -265,31 +282,48 @@ def _scale_normalized_vertices_pdf_annotation(
265282

266283

267284
def _scale_normalized_vertices_image_video_annotation(
268-
annotation: Dict, asset: Dict, is_label_rotated: bool
285+
annotation: Dict, asset: Dict, **kwargs
269286
) -> None:
270287
"""Scale normalized vertices of an image/video object detection annotation."""
271-
if "resolution" not in asset or asset["resolution"] is None:
288+
rotation = kwargs.get("rotation", 0)
289+
normalized_vertices = kwargs.get("normalized_vertices", True)
290+
291+
if not normalized_vertices and ("resolution" not in asset or asset["resolution"] is None):
272292
raise NotCompatibleOptions(
273293
"Image and video labels export with absolute coordinates require `resolution` in the"
274294
" asset. Please use `kili.update_properties_in_assets(resolution_array=...)` to update"
275295
" the resolution of your asset.`"
276296
)
277297

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"]
298+
width = asset["resolution"]["width"] if "resolution" in asset else 0
299+
height = asset["resolution"]["height"] if "resolution" in asset else 0
280300

281301
# bbox, segmentation, polygons
282-
if "boundingPoly" in annotation:
302+
if "boundingPoly" in annotation and normalized_vertices:
283303
annotation["boundingPoly"] = [
284304
{
285-
**norm_vertices_dict, # keep the original normalizedVertices
286-
"vertices": _scale_all_vertices(
287-
norm_vertices_dict["normalizedVertices"], width=width, height=height
305+
"normalizedVertices": reverse_rotation_vertices(
306+
norm_vertices_dict["normalizedVertices"], rotation
288307
),
289308
}
290309
for norm_vertices_dict in annotation["boundingPoly"]
291310
]
311+
return
292312

313+
if "boundingPoly" in annotation and not normalized_vertices:
314+
annotation["boundingPoly"] = [
315+
{
316+
"normalizedVertices": reverse_rotation_vertices(
317+
norm_vertices_dict["normalizedVertices"], rotation
318+
),
319+
"vertices": _scale_all_vertices(
320+
reverse_rotation_vertices(norm_vertices_dict["normalizedVertices"], rotation),
321+
width=width,
322+
height=height,
323+
),
324+
}
325+
for norm_vertices_dict in annotation["boundingPoly"]
326+
]
293327
# point jobs
294328
if "point" in annotation:
295329
annotation["pointPixels"] = _scale_all_vertices(

tests/unit/services/export/test_kili.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def test_preprocess_assets(mocker: pytest_mock.MockFixture):
3333
def test_kili_exporter_convert_to_pixel_coords_pdf(mocker: pytest_mock.MockerFixture):
3434
mocker.patch.object(KiliExporter, "__init__", return_value=None)
3535
exporter = KiliExporter() # type: ignore # pylint: disable=no-value-for-parameter
36+
exporter.normalized_coordinates = None
3637
exporter.project = {
3738
"inputType": "PDF",
3839
"jsonInterface": {

0 commit comments

Comments
 (0)