Skip to content

Commit 2e4ef67

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

File tree

2 files changed

+71
-40
lines changed

2 files changed

+71
-40
lines changed

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

+70-40
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@
22

33
import json
44
from pathlib import Path
5-
from typing import Callable, Dict, List
5+
from typing import Dict, List, Callable
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:
@@ -163,19 +182,15 @@ def _scale_label_vertices(self, label: Dict, asset: Dict) -> None:
163182
" coordinates."
164183
)
165184

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
173188
for job_name in json_resp:
174189
if self._can_scale_vertices_for_job_name(job_name) and json_resp.get(job_name, {}).get(
175190
"annotations"
176191
):
177192
for ann in json_resp[job_name]["annotations"]:
178-
annotation_scaler(ann, asset, is_label_rotated)
193+
annotation_scaler(ann, asset, **kwargs)
179194

180195
def _can_scale_vertices_for_job_name(self, job_name: str) -> bool:
181196
return (
@@ -211,23 +226,23 @@ def _scale_all_vertices(object_, width: int, height: int):
211226
return object_
212227

213228

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:
217230
"""Scale normalized vertices of a PDF annotation.
218231
219232
PDF annotations are different from image annotations because the asset width/height can vary.
220233
221234
PDF only have BBox detection, so we only scale the boundingPoly and polys keys.
222235
"""
236+
is_label_rotated = kwargs.get("is_label_rotated", False)
237+
223238
if is_label_rotated:
224239
raise NotCompatibleOptions("PDF labels cannot be rotated")
225240

226241
if "annotations" in annotation:
227242
# pdf annotations have two layers of "annotations"
228243
# https://docs.kili-technology.com/reference/export-object-entity-detection-and-relation#ner-in-pdfs
229244
for ann in annotation["annotations"]:
230-
_scale_normalized_vertices_pdf_annotation(ann, asset)
245+
_scale_normalized_vertices_pdf_annotation(ann, asset, **kwargs)
231246

232247
# an annotation has three keys:
233248
# - pageNumberArray: list of page numbers
@@ -264,32 +279,47 @@ def _scale_normalized_vertices_pdf_annotation(
264279
]
265280

266281

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:
270283
"""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):
272288
raise NotCompatibleOptions(
273289
"Image and video labels export with absolute coordinates require `resolution` in the"
274290
" asset. Please use `kili.update_properties_in_assets(resolution_array=...)` to update"
275291
" the resolution of your asset.`"
276292
)
277293

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
280296

281297
# bbox, segmentation, polygons
282-
if "boundingPoly" in annotation:
298+
if "boundingPoly" in annotation and normalized_vertices:
283299
annotation["boundingPoly"] = [
284300
{
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
288303
),
289304
}
290305
for norm_vertices_dict in annotation["boundingPoly"]
291306
]
307+
return
292308

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+
]
293323
# point jobs
294324
if "point" in annotation:
295325
annotation["pointPixels"] = _scale_all_vertices(

tests/unit/services/export/test_kili.py

+1
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)