Label utils module
The module kili.utils.labels
provides a set of helpers to convert point, bounding box, polygon and segmentation labels.
Info
In Kili json response format, a normalized vertex is a dictionary with keys x
and y
and values between 0
and 1
. The origin is always the top left corner of the image. The x-axis is horizontal and the y-axis is vertical with the y-axis pointing down. You can find more information about the Kili data format here.
Points
kili.utils.labels.point
Helpers to create point annotations.
normalized_point_to_point(point, img_width=None, img_height=None, origin_location='bottom_left')
Convert a Kili normalized vertex to a 2D point.
It is the inverse of the method point_to_normalized_point
.
A point is a dict with keys "x"
and "y"
, and corresponding values in pixels (int
or float
).
Conventions for the input point:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
Conventions for the output point:
- The origin is defined by the
origin_location
argument. - x-axis is horizontal and goes from left to right.
- y-axis is vertical. If
origin_location
is"top_left"
, it goes from top to bottom. Iforigin_location
is"bottom_left"
, it goes from bottom to top.
If the image width and height are provided, the output point coordinates will be scaled to the image size. If not, the method will return a point with normalized coordinates.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
point |
Dict[str, float] |
Point to convert. |
required |
img_width |
Union[int, float] |
Width of the image the point is defined in. |
None |
img_height |
Union[int, float] |
Height of the image the point is defined in. |
None |
origin_location |
Literal['top_left', 'bottom_left'] |
Location of the origin of output point coordinate system. Can be either |
'bottom_left' |
Returns:
Type | Description |
---|---|
Dict[Literal['x', 'y'], float] |
A dict with keys |
Source code in kili/utils/labels/point.py
def normalized_point_to_point(
point: Dict[str, float],
img_width: Optional[Union[int, float]] = None,
img_height: Optional[Union[int, float]] = None,
origin_location: Literal["top_left", "bottom_left"] = "bottom_left",
) -> Dict[Literal["x", "y"], float]:
# pylint: disable=line-too-long
"""Convert a Kili normalized vertex to a 2D point.
It is the inverse of the method `point_to_normalized_point`.
A point is a dict with keys `"x"` and `"y"`, and corresponding values in pixels (`int` or `float`).
Conventions for the input point:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
Conventions for the output point:
- The origin is defined by the `origin_location` argument.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical. If `origin_location` is `"top_left"`, it goes from top to bottom. If `origin_location` is `"bottom_left"`, it goes from bottom to top.
If the image width and height are provided, the output point coordinates will be scaled to the image size.
If not, the method will return a point with normalized coordinates.
Args:
point: Point to convert.
img_width: Width of the image the point is defined in.
img_height: Height of the image the point is defined in.
origin_location: Location of the origin of output point coordinate system. Can be either `top_left` or `bottom_left`.
Returns:
A dict with keys `"x"` and `"y"`, and corresponding values in pixels.
"""
if (img_width is None) != (img_height is None):
raise ValueError("img_width and img_height must be both None or both not None.")
if origin_location == "bottom_left":
point = {"x": point["x"], "y": 1 - point["y"]}
img_height = img_height or 1
img_width = img_width or 1
return {"x": point["x"] * img_width, "y": point["y"] * img_height}
point_to_normalized_point(point, img_width=None, img_height=None, origin_location='bottom_left')
Converts a 2D point to a Kili normalized vertex.
The output can be used to create object detection annotations. See the documentation for more details.
A point is a dict with keys "x"
and "y"
, and corresponding values in pixels (int
or float
).
Conventions for the input point:
- The origin is defined by the
origin_location
argument. - x-axis is horizontal and goes from left to right.
- y-axis is vertical. If
origin_location
is"top_left"
, it goes from top to bottom. Iforigin_location
is"bottom_left"
, it goes from bottom to top.
Conventions for the output point:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
If the image width and height are provided, the input point coordinates will be normalized to [0, 1]
.
If not, the method expects the input point coordinates to be already normalized.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
point |
Dict[str, Union[int, float]] |
Point to convert. |
required |
img_width |
Union[int, float] |
Width of the image the point is defined in. |
None |
img_height |
Union[int, float] |
Height of the image the point is defined in. |
None |
origin_location |
Literal['top_left', 'bottom_left'] |
Location of the origin of input point coordinate system. Can be either |
'bottom_left' |
Returns:
Type | Description |
---|---|
Dict[Literal['x', 'y'], float] |
A dict with keys |
Example
from kili.utils.labels.point import point_to_normalized_point
normalized_point = point_to_normalized_point({"x": 5, "y": 40}, img_width=100, img_height=100)
json_response = {
"OBJECT_DETECTION_JOB": {
"annotations": [
{
"point": normalized_point,
"categories": [{"name": "CLASS_A"}],
"type": "marker",
}
]
}
}
Source code in kili/utils/labels/point.py
def point_to_normalized_point(
point: Dict[str, Union[int, float]],
img_width: Optional[Union[int, float]] = None,
img_height: Optional[Union[int, float]] = None,
origin_location: Literal["top_left", "bottom_left"] = "bottom_left",
) -> Dict[Literal["x", "y"], float]:
# pylint: disable=line-too-long
"""Converts a 2D point to a Kili normalized vertex.
The output can be used to create object detection annotations. See the [documentation](https://docs.kili-technology.com/reference/export-object-entity-detection-and-relation) for more details.
A point is a dict with keys `"x"` and `"y"`, and corresponding values in pixels (`int` or `float`).
Conventions for the input point:
- The origin is defined by the `origin_location` argument.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical. If `origin_location` is `"top_left"`, it goes from top to bottom. If `origin_location` is `"bottom_left"`, it goes from bottom to top.
Conventions for the output point:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
If the image width and height are provided, the input point coordinates will be normalized to `[0, 1]`.
If not, the method expects the input point coordinates to be already normalized.
Args:
point: Point to convert.
img_width: Width of the image the point is defined in.
img_height: Height of the image the point is defined in.
origin_location: Location of the origin of input point coordinate system. Can be either `top_left` or `bottom_left`.
Returns:
A dict with keys `"x"` and `"y"`, and corresponding normalized values.
!!! Example
```python
from kili.utils.labels.point import point_to_normalized_point
normalized_point = point_to_normalized_point({"x": 5, "y": 40}, img_width=100, img_height=100)
json_response = {
"OBJECT_DETECTION_JOB": {
"annotations": [
{
"point": normalized_point,
"categories": [{"name": "CLASS_A"}],
"type": "marker",
}
]
}
}
```
"""
if (img_width is None) != (img_height is None):
raise ValueError("img_width and img_height must be both None or both not None.")
if img_width is not None and img_height is not None:
point = {
"x": point["x"] / img_width,
"y": point["y"] / img_height,
}
if origin_location == "bottom_left":
point = {"x": point["x"], "y": 1 - point["y"]}
assert 0 <= point["x"] <= 1, f"Point x coordinate {point['x']} should be in [0, 1]."
assert 0 <= point["y"] <= 1, f"Point y coordinate {point['y']} should be in [0, 1]."
return {"x": point["x"], "y": point["y"]}
Bounding boxes
kili.utils.labels.bbox
Helpers to create boundingPoly rectangle annotations.
bbox_points_to_normalized_vertices(*, bottom_left, bottom_right, top_right, top_left, img_width=None, img_height=None, origin_location='bottom_left')
Converts a bounding box defined by its 4 points to normalized vertices.
The output can be used to create a boundingPoly rectangle annotation. See the documentation for more details.
A point is a dict with keys "x"
and "y"
, and corresponding values in pixels (int
or float
).
Conventions for the input points:
- The origin is defined by the
origin_location
argument. - x-axis is horizontal and goes from left to right.
- y-axis is vertical. If
origin_location
is"top_left"
, it goes from top to bottom. Iforigin_location
is"bottom_left"
, it goes from bottom to top.
Conventions for the output vertices:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
If the image width and height are provided, the input point coordinates will be normalized to [0, 1]
.
If not, the method expects the input points' coordinates to be already normalized.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
bottom_left |
Dict[str, Union[int, float]] |
Bottom left point of the bounding box. |
required |
bottom_right |
Dict[str, Union[int, float]] |
Bottom right point of the bounding box. |
required |
top_right |
Dict[str, Union[int, float]] |
Top right point of the bounding box. |
required |
top_left |
Dict[str, Union[int, float]] |
Top left point of the bounding box. |
required |
img_width |
Union[int, float] |
Width of the image the bounding box is defined in. |
None |
img_height |
Union[int, float] |
Height of the image the bounding box is defined in. |
None |
origin_location |
Literal['top_left', 'bottom_left'] |
Location of the origin of input point coordinate system. Can be either |
'bottom_left' |
Returns:
Type | Description |
---|---|
List[Dict[Literal['x', 'y'], float]] |
A list of normalized vertices. |
Example
from kili.utils.labels.bbox import bbox_points_to_normalized_vertices
inputs = {
bottom_left = {"x": 0, "y": 0},
bottom_right = {"x": 10, "y": 0},
top_right = {"x": 10, "y": 10},
top_left = {"x": 0, "y": 10},
img_width = 100,
img_height = 100,
}
normalized_vertices = bbox_points_to_normalized_vertices(**inputs)
json_response = {
"OBJECT_DETECTION_JOB": {
"annotations": [
{
"boundingPoly": [{"normalizedVertices": normalized_vertices}],
"categories": [{"name": "CLASS_A"}],
"type": "rectangle",
}
]
}
}
Source code in kili/utils/labels/bbox.py
def bbox_points_to_normalized_vertices(
*,
bottom_left: Dict[str, Union[int, float]],
bottom_right: Dict[str, Union[int, float]],
top_right: Dict[str, Union[int, float]],
top_left: Dict[str, Union[int, float]],
img_width: Optional[Union[int, float]] = None,
img_height: Optional[Union[int, float]] = None,
origin_location: Literal["top_left", "bottom_left"] = "bottom_left",
) -> List[Dict[Literal["x", "y"], float]]:
# pylint: disable=line-too-long
"""Converts a bounding box defined by its 4 points to normalized vertices.
The output can be used to create a boundingPoly rectangle annotation. See the [documentation](https://docs.kili-technology.com/reference/export-object-entity-detection-and-relation#standard-object-detection) for more details.
A point is a dict with keys `"x"` and `"y"`, and corresponding values in pixels (`int` or `float`).
Conventions for the input points:
- The origin is defined by the `origin_location` argument.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical. If `origin_location` is `"top_left"`, it goes from top to bottom. If `origin_location` is `"bottom_left"`, it goes from bottom to top.
Conventions for the output vertices:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
If the image width and height are provided, the input point coordinates will be normalized to `[0, 1]`.
If not, the method expects the input points' coordinates to be already normalized.
Args:
bottom_left: Bottom left point of the bounding box.
bottom_right: Bottom right point of the bounding box.
top_right: Top right point of the bounding box.
top_left: Top left point of the bounding box.
img_width: Width of the image the bounding box is defined in.
img_height: Height of the image the bounding box is defined in.
origin_location: Location of the origin of input point coordinate system. Can be either `top_left` or `bottom_left`.
Returns:
A list of normalized vertices.
!!! Example
```python
from kili.utils.labels.bbox import bbox_points_to_normalized_vertices
inputs = {
bottom_left = {"x": 0, "y": 0},
bottom_right = {"x": 10, "y": 0},
top_right = {"x": 10, "y": 10},
top_left = {"x": 0, "y": 10},
img_width = 100,
img_height = 100,
}
normalized_vertices = bbox_points_to_normalized_vertices(**inputs)
json_response = {
"OBJECT_DETECTION_JOB": {
"annotations": [
{
"boundingPoly": [{"normalizedVertices": normalized_vertices}],
"categories": [{"name": "CLASS_A"}],
"type": "rectangle",
}
]
}
}
```
"""
assert bottom_left["x"] <= bottom_right["x"], "bottom_left.x must be <= bottom_right.x"
assert top_left["x"] <= top_right["x"], "top_left.x must be <= top_right.x"
if origin_location == "bottom_left":
assert bottom_left["y"] <= top_left["y"], "bottom_left.y must be <= top_left.y"
assert bottom_right["y"] <= top_right["y"], "bottom_right.y must be <= top_right.y"
elif origin_location == "top_left":
assert bottom_left["y"] >= top_left["y"], "bottom_left.y must be >= top_left.y"
assert bottom_right["y"] >= top_right["y"], "bottom_right.y must be >= top_right.y"
if (img_width is None) != (img_height is None):
raise ValueError("img_width and img_height must be both None or both not None.")
return [
point_to_normalized_point(
point, img_width=img_width, img_height=img_height, origin_location=origin_location
)
for point in (bottom_left, top_left, top_right, bottom_right)
]
normalized_vertices_to_bbox_points(normalized_vertices, img_width=None, img_height=None, origin_location='bottom_left')
Converts a rectangle normalizedVertices annotation to a bounding box defined by 4 points.
It is the inverse of the method bbox_points_to_normalized_vertices
.
A point is a dict with keys "x"
and "y"
, and corresponding values in pixels (int
or float
).
Conventions for the input vertices:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
Conventions for the output points (top_left
, bottom_left
, bottom_right
, top_right
):
- The origin is defined by the
origin_location
argument. - x-axis is horizontal and goes from left to right.
- y-axis is vertical. If
origin_location
is"top_left"
, it goes from top to bottom. Iforigin_location
is"bottom_left"
, it goes from bottom to top.
If the image width and height are provided, the output point coordinates will be scaled to the image size.
If not, the method will return the output points' coordinates normalized to [0, 1]
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
normalized_vertices |
List[Dict[str, float]] |
A list of normalized vertices. |
required |
img_width |
Union[int, float] |
Width of the image the bounding box is defined in. |
None |
img_height |
Union[int, float] |
Height of the image the bounding box is defined in. |
None |
origin_location |
Literal['top_left', 'bottom_left'] |
Location of the origin of output point coordinate system. Can be either |
'bottom_left' |
Returns:
Type | Description |
---|---|
Dict[Literal['top_left', 'bottom_left', 'bottom_right', 'top_right'], Dict[Literal['x', 'y'], float]] |
A dict with keys |
Example
from kili.utils.labels.bbox import normalized_vertices_to_bbox_points
# if using raw dict label:
normalized_vertices = label["jsonResponse"]["OBJECT_DETECTION_JOB"]["annotations"][0]["boundingPoly"][0]["normalizedVertices"]
# if using parsed label:
normalized_vertices = label.jobs["OBJECT_DETECTION_JOB"].annotations[0].bounding_poly[0].normalized_vertices
img_height, img_width = 1080, 1920
bbox_points = normalized_vertices_to_bbox_points(normalized_vertices, img_width, img_height)
Source code in kili/utils/labels/bbox.py
def normalized_vertices_to_bbox_points(
normalized_vertices: List[Dict[str, float]],
img_width: Optional[Union[int, float]] = None,
img_height: Optional[Union[int, float]] = None,
origin_location: Literal["top_left", "bottom_left"] = "bottom_left",
) -> Dict[
Literal["top_left", "bottom_left", "bottom_right", "top_right"], Dict[Literal["x", "y"], float]
]:
# pylint: disable=line-too-long
"""Converts a rectangle normalizedVertices annotation to a bounding box defined by 4 points.
It is the inverse of the method `bbox_points_to_normalized_vertices`.
A point is a dict with keys `"x"` and `"y"`, and corresponding values in pixels (`int` or `float`).
Conventions for the input vertices:
- The origin is the top left corner of the image.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical and goes from top to bottom.
Conventions for the output points (`top_left`, `bottom_left`, `bottom_right`, `top_right`):
- The origin is defined by the `origin_location` argument.
- x-axis is horizontal and goes from left to right.
- y-axis is vertical. If `origin_location` is `"top_left"`, it goes from top to bottom. If `origin_location` is `"bottom_left"`, it goes from bottom to top.
If the image width and height are provided, the output point coordinates will be scaled to the image size.
If not, the method will return the output points' coordinates normalized to `[0, 1]`.
Args:
normalized_vertices: A list of normalized vertices.
img_width: Width of the image the bounding box is defined in.
img_height: Height of the image the bounding box is defined in.
origin_location: Location of the origin of output point coordinate system. Can be either `top_left` or `bottom_left`.
Returns:
A dict with keys `"top_left"`, `"bottom_left"`, `"bottom_right"`, `"top_right"`, and corresponding points.
!!! Example
```python
from kili.utils.labels.bbox import normalized_vertices_to_bbox_points
# if using raw dict label:
normalized_vertices = label["jsonResponse"]["OBJECT_DETECTION_JOB"]["annotations"][0]["boundingPoly"][0]["normalizedVertices"]
# if using parsed label:
normalized_vertices = label.jobs["OBJECT_DETECTION_JOB"].annotations[0].bounding_poly[0].normalized_vertices
img_height, img_width = 1080, 1920
bbox_points = normalized_vertices_to_bbox_points(normalized_vertices, img_width, img_height)
```
"""
if len(normalized_vertices) != 4:
raise ValueError(f"normalized_vertices must have length 4. Got {len(normalized_vertices)}.")
if (img_width is None) != (img_height is None):
raise ValueError("img_width and img_height must be both None or both not None.")
img_height = img_height or 1
img_width = img_width or 1
ret = {}
for vertex, point_name in zip(
normalized_vertices, ("bottom_left", "top_left", "top_right", "bottom_right")
):
ret[point_name] = normalized_point_to_point(
vertex, img_width=img_width, img_height=img_height, origin_location=origin_location
)
return ret
Polygon and segmentation masks
kili.utils.labels.image
OpenCV
It is recommended to install the image dependencies to use the image helpers.
pip install kili[image-utils]
Helpers to create boundingPoly polygon and semantic annotations.
mask_to_normalized_vertices(image)
Converts a binary mask to a list of normalized vertices using OpenCV cv2.findContours.
The output can be used to create "boundingPoly" polygon or semantic annotations. See the documentation for more details.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
image |
ndarray |
Binary mask. Should be an array of shape (height, width) with values in {0, 255}. |
required |
Returns:
Type | Description |
---|---|
Tuple |
A tuple containing a list of normalized vertices and the hierarchy of the contours (see OpenCV documentation). |
Example
import urllib.request
import cv2
from kili.utils.labels.image import mask_to_normalized_vertices
mask_url = "https://raw.githubusercontent.com/kili-technology/kili-python-sdk/main/recipes/img/HUMAN.mask.png"
urllib.request.urlretrieve(mask_url, "mask.png")
img = cv2.imread("mask.png")[:, :, 0] # keep only height and width
img[200:220, 200:220] = 0 # add a hole in the mask to test the hierarchy
contours, hierarchy = mask_to_normalized_vertices(img)
# hierarchy tells us that the first contour is the outer contour
# and the second one is the inner contour
json_response = {
"OBJECT_DETECTION_JOB": {
"annotations": [
{
"boundingPoly": [
{"normalizedVertices": contours[0]}, # outer contour
{"normalizedVertices": contours[1]}, # inner contour
],
"categories": [{"name": "A"}],
"type": "semantic",
}
]
}
}
Source code in kili/utils/labels/image.py
def mask_to_normalized_vertices(
image: np.ndarray,
) -> Tuple[List[List[Dict[str, float]]], np.ndarray]:
# pylint: disable=line-too-long
"""Converts a binary mask to a list of normalized vertices using OpenCV [cv2.findContours](https://docs.opencv.org/4.7.0/d3/dc0/group__imgproc__shape.html#gadf1ad6a0b82947fa1fe3c3d497f260e0).
The output can be used to create "boundingPoly" polygon or semantic annotations.
See the [documentation](https://docs.kili-technology.com/reference/export-object-entity-detection-and-relation#standard-object-detection) for more details.
Args:
image: Binary mask. Should be an array of shape (height, width) with values in {0, 255}.
Returns:
Tuple: A tuple containing a list of normalized vertices and the hierarchy of the contours (see [OpenCV documentation](https://docs.opencv.org/4.7.0/d9/d8b/tutorial_py_contours_hierarchy.html)).
!!! Example
```python
import urllib.request
import cv2
from kili.utils.labels.image import mask_to_normalized_vertices
mask_url = "https://raw.githubusercontent.com/kili-technology/kili-python-sdk/main/recipes/img/HUMAN.mask.png"
urllib.request.urlretrieve(mask_url, "mask.png")
img = cv2.imread("mask.png")[:, :, 0] # keep only height and width
img[200:220, 200:220] = 0 # add a hole in the mask to test the hierarchy
contours, hierarchy = mask_to_normalized_vertices(img)
# hierarchy tells us that the first contour is the outer contour
# and the second one is the inner contour
json_response = {
"OBJECT_DETECTION_JOB": {
"annotations": [
{
"boundingPoly": [
{"normalizedVertices": contours[0]}, # outer contour
{"normalizedVertices": contours[1]}, # inner contour
],
"categories": [{"name": "A"}],
"type": "semantic",
}
]
}
}
```
"""
if image.ndim > 2:
raise ValueError(f"Image should be a 2D array, got {image.ndim}D array")
unique_values = np.unique(image).tolist()
if not all(value in [0, 255] for value in unique_values):
raise ValueError(f"Image should be binary with values in {{0, 255}}, got {unique_values}")
img_height, img_width = image.shape
# pylint:disable=no-member
contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # type: ignore
contours = [
_opencv_contour_to_normalized_vertices(contour, img_width, img_height)
for contour in contours
]
hierarchy = hierarchy[0]
return contours, hierarchy
normalized_vertices_to_mask(normalized_vertices, img_width, img_height)
Converts a Kili label with normalized vertices to a binary mask.
It is the inverse of the method mask_to_normalized_vertices
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
normalized_vertices |
List[Dict[str, float]] |
A list of normalized vertices. |
required |
img_width |
int |
Width of the image the segmentation is defined in. |
required |
img_height |
int |
Height of the image the segmentation is defined in. |
required |
Returns:
Type | Description |
---|---|
ndarray |
A numpy array of shape (height, width) with values in {0, 255}. |
Example
from kili.utils.labels.image import normalized_vertices_to_mask
# if using raw dict label:
normalized_vertices = label["jsonResponse"]["OBJECT_DETECTION_JOB"]["annotations"][0]["boundingPoly"][0]["normalizedVertices"]
# if using parsed label:
normalized_vertices = label.jobs["OBJECT_DETECTION_JOB"].annotations[0].bounding_poly[0].normalized_vertices
img_height, img_width = 1080, 1920
mask = normalized_vertices_to_mask(normalized_vertices, img_width, img_height)
plt.imshow(mask)
plt.show()
Source code in kili/utils/labels/image.py
def normalized_vertices_to_mask(
normalized_vertices: List[Dict[str, float]], img_width: int, img_height: int
) -> np.ndarray:
# pylint: disable=line-too-long
"""Converts a Kili label with normalized vertices to a binary mask.
It is the inverse of the method `mask_to_normalized_vertices`.
Args:
normalized_vertices: A list of normalized vertices.
img_width: Width of the image the segmentation is defined in.
img_height: Height of the image the segmentation is defined in.
Returns:
A numpy array of shape (height, width) with values in {0, 255}.
!!! Example
```python
from kili.utils.labels.image import normalized_vertices_to_mask
# if using raw dict label:
normalized_vertices = label["jsonResponse"]["OBJECT_DETECTION_JOB"]["annotations"][0]["boundingPoly"][0]["normalizedVertices"]
# if using parsed label:
normalized_vertices = label.jobs["OBJECT_DETECTION_JOB"].annotations[0].bounding_poly[0].normalized_vertices
img_height, img_width = 1080, 1920
mask = normalized_vertices_to_mask(normalized_vertices, img_width, img_height)
plt.imshow(mask)
plt.show()
```
"""
mask = np.zeros((img_height, img_width), dtype=np.uint8)
polygon = [
[
int(round(vertice["x"] * img_width)),
int(round(vertice["y"] * img_height)),
]
for vertice in normalized_vertices
]
polygon = np.array([polygon])
cv2.fillPoly(img=mask, pts=polygon, color=255) # type: ignore # pylint:disable=no-member
return mask
GeoJson
Info
Label coordinates of GeoTIFF files (with geospatial metadata) are expressed in latitude and longitude where x
stands for longitude and y
for latitude.
Read more about Kili labeling features for geospatial imagery here.
Warning
If the geotiff image asset does not have geospatial metadata, the coordinates will be expressed in normalized coordinates, and the export to GeoJSON will not be accurate since the geospatial information is missing.
To check if your image asset has geospatial metadata, you can use the following code snippet:
>>> asset = kili.assets(..., fields=["jsonContent"])[0]
>>> print(asset['jsonContent'])
# asset without geospatial metadata
[{"imageUrl": "https://...", "initEpsg": -1, "useClassicCoordinates": true}]
# asset with geospatial metadata
# note that the epsg and initEpsg may be different for your asset
[{"bounds": [[...], [...]], "epsg": "EPSG4326", "imageUrl": "https://...", "initEpsg": 4326, "useClassicCoordinates": false}]
Point
Point label utils.
geojson_point_feature_to_kili_point_annotation(point, categories=None, children=None, mid=None)
Convert a geojson point feature to a Kili point annotation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
point |
Dict[str, Any] |
a geojson point feature. |
required |
categories |
Optional[List[Dict]] |
the categories of the annotation.
If not provided, the categories are taken from the |
None |
children |
Optional[Dict] |
the children of the annotation.
If not provided, the children are taken from the |
None |
mid |
Optional[str] |
the mid of the annotation.
If not provided, the mid is taken from the |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Kili point annotation. |
Example
>>> point = {
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [-79.0, -3.0]},
'id': 'mid_object',
'properties': {'kili': {'categories': [{'name': 'A'}]}}
}
>>> geojson_point_feature_to_kili_point_annotation(point)
{
'children': {},
'point': {'x': -79.0, 'y': -3.0},
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'marker'
}
Source code in kili/utils/labels/geojson/point.py
def geojson_point_feature_to_kili_point_annotation(
point: Dict[str, Any],
categories: Optional[List[Dict]] = None,
children: Optional[Dict] = None,
mid: Optional[str] = None,
) -> Dict[str, Any]:
# pylint: disable=line-too-long
"""Convert a geojson point feature to a Kili point annotation.
Args:
point: a geojson point feature.
categories: the categories of the annotation.
If not provided, the categories are taken from the `kili` key of the geojson feature properties.
children: the children of the annotation.
If not provided, the children are taken from the `kili` key of the geojson feature properties.
mid: the mid of the annotation.
If not provided, the mid is taken from the `id` key of the geojson feature.
Returns:
A Kili point annotation.
!!! Example
```python
>>> point = {
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [-79.0, -3.0]},
'id': 'mid_object',
'properties': {'kili': {'categories': [{'name': 'A'}]}}
}
>>> geojson_point_feature_to_kili_point_annotation(point)
{
'children': {},
'point': {'x': -79.0, 'y': -3.0},
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'marker'
}
```
"""
assert point.get("type") == "Feature", f"Feature type must be `Feature`, got: {point['type']}"
assert (
point["geometry"]["type"] == "Point"
), f"Geometry type must be `Point`, got: {point['geometry']['type']}"
children = children or point["properties"].get("kili", {}).get("children", {})
categories = categories or point["properties"]["kili"]["categories"]
ret = {
"children": children,
"categories": categories,
"type": "marker",
}
ret["point"] = {
"x": point["geometry"]["coordinates"][0],
"y": point["geometry"]["coordinates"][1],
}
if mid is not None:
ret["mid"] = str(mid)
elif "id" in point:
ret["mid"] = str(point["id"])
return ret
kili_point_annotation_to_geojson_point_feature(point_annotation, job_name=None)
Convert a Kili point annotation to a geojson point feature.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
point_annotation |
Dict[str, Any] |
a Kili point annotation. |
required |
job_name |
Optional[str] |
the name of the job to which the annotation belongs. |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson point feature. |
Example
>>> point = {
'children': {},
'point': {'x': -79.0, 'y': -3.0},
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'marker'
}
>>> kili_point_annotation_to_geojson_point_feature(point)
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'marker'
}
}
}
}
Source code in kili/utils/labels/geojson/point.py
def kili_point_annotation_to_geojson_point_feature(
point_annotation: Dict[str, Any], job_name: Optional[str] = None
) -> Dict[str, Any]:
"""Convert a Kili point annotation to a geojson point feature.
Args:
point_annotation: a Kili point annotation.
job_name: the name of the job to which the annotation belongs.
Returns:
A geojson point feature.
!!! Example
```python
>>> point = {
'children': {},
'point': {'x': -79.0, 'y': -3.0},
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'marker'
}
>>> kili_point_annotation_to_geojson_point_feature(point)
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'marker'
}
}
}
}
```
"""
point = point_annotation
assert point["type"] == "marker", f"Annotation type must be `marker`, got: {point['type']}"
ret = {"type": "Feature", "geometry": kili_point_to_geojson_point(point["point"])}
if "mid" in point:
ret["id"] = point["mid"]
ret["properties"] = {"kili": {k: v for k, v in point.items() if k not in ["point", "mid"]}}
if job_name is not None:
ret["properties"]["kili"]["job"] = job_name
return ret
kili_point_to_geojson_point(point)
Convert a Kili point to a geojson point.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
point |
Dict[str, float] |
a Kili point (vertex). |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson point. |
Example
>>> point = {"x": 1.0, "y": 2.0}
>>> kili_point_to_geojson_point(point)
{
"type": "Point",
"coordinates": [1.0, 2.0]
}
Source code in kili/utils/labels/geojson/point.py
def kili_point_to_geojson_point(point: Dict[str, float]) -> Dict[str, Any]:
"""Convert a Kili point to a geojson point.
Args:
point: a Kili point (vertex).
Returns:
A geojson point.
!!! Example
```python
>>> point = {"x": 1.0, "y": 2.0}
>>> kili_point_to_geojson_point(point)
{
"type": "Point",
"coordinates": [1.0, 2.0]
}
```
"""
return {"type": "Point", "coordinates": [point["x"], point["y"]]}
Line
Geojson linestring utilities.
geojson_linestring_feature_to_kili_line_annotation(line, categories=None, children=None, mid=None)
Convert a geojson linestring feature to a Kili line annotation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
line |
Dict[str, Any] |
a geojson linestring feature. |
required |
categories |
Optional[List[Dict]] |
the categories of the annotation.
If not provided, the categories are taken from the |
None |
children |
Optional[Dict] |
the children of the annotation.
If not provided, the children are taken from the |
None |
mid |
Optional[str] |
the mid of the annotation.
If not provided, the mid is taken from the |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Kili line annotation. |
Example
>>> line = {
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': [[-79.0, -3.0], [-79.0, -3.0]]},
}
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'job': 'job_name'
}
}
}
>>> geojson_linestring_feature_to_kili_line_annotation(line)
{
'children': {},
'polyline': [{'x': -79.0, 'y': -3.0}, {'x': -79.0, 'y': -3.0}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polyline'
}
Source code in kili/utils/labels/geojson/line.py
def geojson_linestring_feature_to_kili_line_annotation(
line: Dict[str, Any],
categories: Optional[List[Dict]] = None,
children: Optional[Dict] = None,
mid: Optional[str] = None,
) -> Dict[str, Any]:
# pylint: disable=line-too-long
"""Convert a geojson linestring feature to a Kili line annotation.
Args:
line: a geojson linestring feature.
categories: the categories of the annotation.
If not provided, the categories are taken from the `kili` key of the geojson feature properties.
children: the children of the annotation.
If not provided, the children are taken from the `kili` key of the geojson feature properties.
mid: the mid of the annotation.
If not provided, the mid is taken from the `id` key of the geojson feature.
Returns:
A Kili line annotation.
!!! Example
```python
>>> line = {
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': [[-79.0, -3.0], [-79.0, -3.0]]},
}
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'job': 'job_name'
}
}
}
>>> geojson_linestring_feature_to_kili_line_annotation(line)
{
'children': {},
'polyline': [{'x': -79.0, 'y': -3.0}, {'x': -79.0, 'y': -3.0}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polyline'
}
```
"""
assert line["type"] == "Feature", f"Feature type must be `Feature`, got: {line['type']}"
assert (
line["geometry"]["type"] == "LineString"
), f"Geometry type must be `LineString`, got: {line['geometry']['type']}"
children = children or line["properties"].get("kili", {}).get("children", {})
categories = categories or line["properties"]["kili"]["categories"]
ret = {
"children": children,
"categories": categories,
"type": "polyline",
}
ret["polyline"] = [{"x": coord[0], "y": coord[1]} for coord in line["geometry"]["coordinates"]]
if mid is not None:
ret["mid"] = str(mid)
elif "id" in line:
ret["mid"] = str(line["id"])
return ret
kili_line_annotation_to_geojson_linestring_feature(polyline_annotation, job_name=None)
Convert a Kili line annotation to a geojson linestring feature.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
polyline_annotation |
Dict[str, Any] |
a Kili line annotation. |
required |
job_name |
Optional[str] |
the name of the job to which the annotation belongs. |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson linestring feature. |
Example
>>> polyline = {
'children': {},
'polyline': [{'x': -79.0, 'y': -3.0}, {'x': -79.0, 'y': -3.0}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polyline'
}
>>> kili_line_annotation_to_geojson_linestring_feature(polyline, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': [[-79.0, -3.0], [-79.0, -3.0]]},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'job': 'job_name'
}
}
}
Source code in kili/utils/labels/geojson/line.py
def kili_line_annotation_to_geojson_linestring_feature(
polyline_annotation: Dict[str, Any], job_name: Optional[str] = None
) -> Dict[str, Any]:
"""Convert a Kili line annotation to a geojson linestring feature.
Args:
polyline_annotation: a Kili line annotation.
job_name: the name of the job to which the annotation belongs.
Returns:
A geojson linestring feature.
!!! Example
```python
>>> polyline = {
'children': {},
'polyline': [{'x': -79.0, 'y': -3.0}, {'x': -79.0, 'y': -3.0}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polyline'
}
>>> kili_line_annotation_to_geojson_linestring_feature(polyline, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': [[-79.0, -3.0], [-79.0, -3.0]]},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'job': 'job_name'
}
}
}
```
"""
assert (
polyline_annotation["type"] == "polyline"
), f"Annotation type must be `polyline`, got: {polyline_annotation['type']}"
ret = {
"type": "Feature",
"geometry": kili_line_to_geojson_linestring(polyline_annotation["polyline"]),
}
if "mid" in polyline_annotation:
ret["id"] = polyline_annotation["mid"]
ret["properties"] = {
"kili": {k: v for k, v in polyline_annotation.items() if k not in ["mid", "polyline"]}
}
if job_name is not None:
ret["properties"]["kili"]["job"] = job_name
return ret
kili_line_to_geojson_linestring(polyline)
Convert a Kili line to a geojson linestring.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
polyline |
List[Dict[str, float]] |
a Kili line (polyline). |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson linestring. |
Example
>>> polyline = [{"x": 1.0, "y": 2.0}, {"x": 3.0, "y": 4.0}]
>>> kili_line_to_geojson_linestring(polyline)
{
"type": "LineString",
"coordinates": [[1.0, 2.0], [3.0, 4.0]]
}
Source code in kili/utils/labels/geojson/line.py
def kili_line_to_geojson_linestring(polyline: List[Dict[str, float]]) -> Dict[str, Any]:
"""Convert a Kili line to a geojson linestring.
Args:
polyline: a Kili line (polyline).
Returns:
A geojson linestring.
!!! Example
```python
>>> polyline = [{"x": 1.0, "y": 2.0}, {"x": 3.0, "y": 4.0}]
>>> kili_line_to_geojson_linestring(polyline)
{
"type": "LineString",
"coordinates": [[1.0, 2.0], [3.0, 4.0]]
}
```
"""
ret = {"type": "LineString", "coordinates": []}
ret["coordinates"] = [[vertex["x"], vertex["y"]] for vertex in polyline]
return ret # type: ignore
Bounding box
Bounding box conversion functions between Kili and geojson formats.
geojson_polygon_feature_to_kili_bbox_annotation(polygon, categories=None, children=None, mid=None)
Convert a geojson polygon feature to a Kili bounding box annotation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
polygon |
Dict[str, Any] |
a geojson polygon feature. |
required |
categories |
Optional[List[Dict]] |
the categories of the annotation.
If not provided, the categories are taken from the |
None |
children |
Optional[Dict] |
the children of the annotation.
If not provided, the children are taken from the |
None |
mid |
Optional[str] |
the mid of the annotation.
If not provided, the mid is taken from the |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Kili bounding box annotation. |
Example
>>> polygon = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
[-12.6, 12.87],
[-42.6, 22.17],
[-17.6, -22.4],
[2.6, -1.87],
[-12.6, 12.87]
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'rectangle',
'job': 'job_name'
}
}
}
>>> geojson_polygon_feature_to_kili_bbox_annotation(polygon)
{
'children': {},
'boundingPoly': [
{
'normalizedVertices': [
{'x': -12.6, 'y': 12.87},
{'x': -42.6, 'y': 22.17},
{'x': -17.6, 'y': -22.4},
{'x': 2.6, 'y': -1.87}
]
}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'rectangle'
}
Source code in kili/utils/labels/geojson/bbox.py
def geojson_polygon_feature_to_kili_bbox_annotation(
polygon: Dict[str, Any],
categories: Optional[List[Dict]] = None,
children: Optional[Dict] = None,
mid: Optional[str] = None,
) -> Dict[str, Any]:
# pylint: disable=line-too-long
"""Convert a geojson polygon feature to a Kili bounding box annotation.
Args:
polygon: a geojson polygon feature.
categories: the categories of the annotation.
If not provided, the categories are taken from the `kili` key of the geojson feature properties.
children: the children of the annotation.
If not provided, the children are taken from the `kili` key of the geojson feature properties.
mid: the mid of the annotation.
If not provided, the mid is taken from the `id` key of the geojson feature.
Returns:
A Kili bounding box annotation.
!!! Example
```python
>>> polygon = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
[-12.6, 12.87],
[-42.6, 22.17],
[-17.6, -22.4],
[2.6, -1.87],
[-12.6, 12.87]
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'rectangle',
'job': 'job_name'
}
}
}
>>> geojson_polygon_feature_to_kili_bbox_annotation(polygon)
{
'children': {},
'boundingPoly': [
{
'normalizedVertices': [
{'x': -12.6, 'y': 12.87},
{'x': -42.6, 'y': 22.17},
{'x': -17.6, 'y': -22.4},
{'x': 2.6, 'y': -1.87}
]
}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'rectangle'
}
```
"""
assert (
polygon.get("type") == "Feature"
), f"Feature type must be `Feature`, got: {polygon['type']}"
assert (
polygon["geometry"]["type"] == "Polygon"
), f"Geometry type must be `Polygon`, got: {polygon['geometry']['type']}"
children = children or polygon["properties"].get("kili", {}).get("children", {})
categories = categories or polygon["properties"]["kili"]["categories"]
ret = {
"children": children,
"categories": categories,
"type": "rectangle",
}
# geojson polygon has one more point than kili bounding box
coords = polygon["geometry"]["coordinates"][0]
normalized_vertices = [
{"x": coords[0][0], "y": coords[0][1]},
{"x": coords[3][0], "y": coords[3][1]},
{"x": coords[2][0], "y": coords[2][1]},
{"x": coords[1][0], "y": coords[1][1]},
]
ret["boundingPoly"] = [{"normalizedVertices": normalized_vertices}]
if mid is not None:
ret["mid"] = mid
elif "id" in polygon:
ret["mid"] = polygon["id"]
return ret
kili_bbox_annotation_to_geojson_polygon_feature(bbox_annotation, job_name=None)
Convert a Kili bounding box annotation to a geojson polygon feature.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
bbox_annotation |
Dict[str, Any] |
a Kili bounding box annotation. |
required |
job_name |
Optional[str] |
the name of the job to which the annotation belongs. |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson polygon feature. |
Example
>>> bbox = {
'children': {},
'boundingPoly': [
{
'normalizedVertices': [
{'x': -12.6, 'y': 12.87},
{'x': -42.6, 'y': 22.17},
{'x': -17.6, 'y': -22.4},
{'x': 2.6, 'y': -1.87}
]
}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'rectangle'
}
>>> kili_bbox_annotation_to_geojson_polygon_feature(bbox, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
[-12.6, 12.87],
[-42.6, 22.17],
[-17.6, -22.4],
[2.6, -1.87],
[-12.6, 12.87]
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'rectangle',
'job': 'job_name'
}
}
}
Source code in kili/utils/labels/geojson/bbox.py
def kili_bbox_annotation_to_geojson_polygon_feature(
bbox_annotation: Dict[str, Any], job_name: Optional[str] = None
) -> Dict[str, Any]:
"""Convert a Kili bounding box annotation to a geojson polygon feature.
Args:
bbox_annotation: a Kili bounding box annotation.
job_name: the name of the job to which the annotation belongs.
Returns:
A geojson polygon feature.
!!! Example
```python
>>> bbox = {
'children': {},
'boundingPoly': [
{
'normalizedVertices': [
{'x': -12.6, 'y': 12.87},
{'x': -42.6, 'y': 22.17},
{'x': -17.6, 'y': -22.4},
{'x': 2.6, 'y': -1.87}
]
}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'rectangle'
}
>>> kili_bbox_annotation_to_geojson_polygon_feature(bbox, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
[-12.6, 12.87],
[-42.6, 22.17],
[-17.6, -22.4],
[2.6, -1.87],
[-12.6, 12.87]
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'rectangle',
'job': 'job_name'
}
}
}
```
"""
bbox = bbox_annotation
assert bbox["type"] == "rectangle", f"Annotation type must be `rectangle`, got: {bbox['type']}"
ret = {
"type": "Feature",
"geometry": kili_bbox_to_geojson_polygon(bbox["boundingPoly"][0]["normalizedVertices"]),
}
if "mid" in bbox:
ret["id"] = bbox["mid"]
ret["properties"] = {
"kili": {k: v for k, v in bbox.items() if k not in ["boundingPoly", "mid"]}
}
if job_name is not None:
ret["properties"]["kili"]["job"] = job_name
return ret
kili_bbox_to_geojson_polygon(vertices)
Convert a Kili bounding box to a geojson polygon.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
vertices |
List[Dict[str, float]] |
Kili bounding polygon vertices. |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson polygon. |
Example
>>> vertices = [
{'x': 12.0, 'y': 3.0},
{'x': 12.0, 'y': 4.0},
{'x': 13.0, 'y': 4.0},
{'x': 13.0, 'y': 3.0}
]
>>> kili_bbox_to_geojson_polygon(vertices)
{
'type': 'Polygon',
'coordinates': [
[
[12.0, 3.0],
[12.0, 4.0],
[13.0, 4.0],
[13.0, 3.0],
[12.0, 3.0]
]
]
}
Source code in kili/utils/labels/geojson/bbox.py
def kili_bbox_to_geojson_polygon(vertices: List[Dict[str, float]]) -> Dict[str, Any]:
"""Convert a Kili bounding box to a geojson polygon.
Args:
vertices: Kili bounding polygon vertices.
Returns:
A geojson polygon.
!!! Example
```python
>>> vertices = [
{'x': 12.0, 'y': 3.0},
{'x': 12.0, 'y': 4.0},
{'x': 13.0, 'y': 4.0},
{'x': 13.0, 'y': 3.0}
]
>>> kili_bbox_to_geojson_polygon(vertices)
{
'type': 'Polygon',
'coordinates': [
[
[12.0, 3.0],
[12.0, 4.0],
[13.0, 4.0],
[13.0, 3.0],
[12.0, 3.0]
]
]
}
```
"""
vertex_name_to_value = {}
for vertex, point_name in zip(
vertices, ("bottom_left", "top_left", "top_right", "bottom_right")
):
vertex_name_to_value[point_name] = vertex
ret = {"type": "Polygon", "coordinates": []}
ret["coordinates"] = [
[
[vertex_name_to_value["bottom_left"]["x"], vertex_name_to_value["bottom_left"]["y"]],
[vertex_name_to_value["bottom_right"]["x"], vertex_name_to_value["bottom_right"]["y"]],
[vertex_name_to_value["top_right"]["x"], vertex_name_to_value["top_right"]["y"]],
[vertex_name_to_value["top_left"]["x"], vertex_name_to_value["top_left"]["y"]],
[vertex_name_to_value["bottom_left"]["x"], vertex_name_to_value["bottom_left"]["y"]],
]
]
return ret
Polygon
Polygon label utils.
geojson_polygon_feature_to_kili_polygon_annotation(polygon, categories=None, children=None, mid=None)
Convert a geojson polygon feature to a Kili polygon annotation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
polygon |
Dict[str, Any] |
a geojson polygon feature. |
required |
categories |
Optional[List[Dict]] |
the categories of the annotation.
If not provided, the categories are taken from the |
None |
children |
Optional[Dict] |
the children of the annotation.
If not provided, the children are taken from the |
None |
mid |
Optional[str] |
the mid of the annotation.
If not provided, the mid is taken from the |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Kili polygon annotation. |
Example
>>> polygon = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [[[-79.0, -3.0], [-79.0, -3.0]]]},
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'polygon',
'job': 'job_name'
}
}
}
>>> geojson_polygon_feature_to_kili_polygon_annotation(polygon)
{
'children': {},
'boundingPoly': [{'normalizedVertices': [{'x': -79.0, 'y': -3.0}]}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polygon'
}
Source code in kili/utils/labels/geojson/polygon.py
def geojson_polygon_feature_to_kili_polygon_annotation(
polygon: Dict[str, Any],
categories: Optional[List[Dict]] = None,
children: Optional[Dict] = None,
mid: Optional[str] = None,
) -> Dict[str, Any]:
# pylint: disable=line-too-long
"""Convert a geojson polygon feature to a Kili polygon annotation.
Args:
polygon: a geojson polygon feature.
categories: the categories of the annotation.
If not provided, the categories are taken from the `kili` key of the geojson feature properties.
children: the children of the annotation.
If not provided, the children are taken from the `kili` key of the geojson feature properties.
mid: the mid of the annotation.
If not provided, the mid is taken from the `id` key of the geojson feature.
Returns:
A Kili polygon annotation.
!!! Example
```python
>>> polygon = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [[[-79.0, -3.0], [-79.0, -3.0]]]},
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'polygon',
'job': 'job_name'
}
}
}
>>> geojson_polygon_feature_to_kili_polygon_annotation(polygon)
{
'children': {},
'boundingPoly': [{'normalizedVertices': [{'x': -79.0, 'y': -3.0}]}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polygon'
}
```
"""
assert (
polygon.get("type") == "Feature"
), f"Feature type must be `Feature`, got: {polygon['type']}"
assert (
polygon["geometry"]["type"] == "Polygon"
), f"Geometry type must be `Polygon`, got: {polygon['geometry']['type']}"
children = children or polygon["properties"].get("kili", {}).get("children", {})
categories = categories or polygon["properties"]["kili"]["categories"]
ret = {
"children": children,
"categories": categories,
"type": "polygon",
}
coords = polygon["geometry"]["coordinates"][0]
normalized_vertices = [{"x": coord[0], "y": coord[1]} for coord in coords[:-1]]
ret["boundingPoly"] = [{"normalizedVertices": normalized_vertices}]
if mid is not None:
ret["mid"] = str(mid)
elif "id" in polygon:
ret["mid"] = str(polygon["id"])
return ret
kili_polygon_annotation_to_geojson_polygon_feature(polygon_annotation, job_name=None)
Convert a Kili polygon annotation to a geojson polygon feature.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
polygon_annotation |
Dict[str, Any] |
a Kili polygon annotation. |
required |
job_name |
Optional[str] |
the name of the job to which the annotation belongs. |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson polygon feature. |
Example
>>> polygon = {
'children': {},
'boundingPoly': [{'normalizedVertices': [{'x': -79.0, 'y': -3.0}]}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polygon'
}
>>> kili_polygon_annotation_to_geojson_polygon_feature(polygon, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [[[-79.0, -3.0], [-79.0, -3.0]]]},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'polygon',
'job': 'job_name'
}
}
}
}
Source code in kili/utils/labels/geojson/polygon.py
def kili_polygon_annotation_to_geojson_polygon_feature(
polygon_annotation: Dict[str, Any], job_name: Optional[str] = None
) -> Dict[str, Any]:
"""Convert a Kili polygon annotation to a geojson polygon feature.
Args:
polygon_annotation: a Kili polygon annotation.
job_name: the name of the job to which the annotation belongs.
Returns:
A geojson polygon feature.
!!! Example
```python
>>> polygon = {
'children': {},
'boundingPoly': [{'normalizedVertices': [{'x': -79.0, 'y': -3.0}]}],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'polygon'
}
>>> kili_polygon_annotation_to_geojson_polygon_feature(polygon, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [[[-79.0, -3.0], [-79.0, -3.0]]]},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'polygon',
'job': 'job_name'
}
}
}
}
```
"""
polygon = polygon_annotation
assert (
polygon["type"] == "polygon"
), f"Annotation type must be `polygon`, got: {polygon['type']}"
ret = {
"type": "Feature",
"geometry": kili_polygon_to_geojson_polygon(
polygon["boundingPoly"][0]["normalizedVertices"]
),
}
if "mid" in polygon:
ret["id"] = polygon["mid"]
ret["properties"] = {
"kili": {k: v for k, v in polygon.items() if k not in ["boundingPoly", "mid"]}
}
if job_name is not None:
ret["properties"]["kili"]["job"] = job_name
return ret
kili_polygon_to_geojson_polygon(vertices)
Convert a Kili polygon to a geojson polygon.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
vertices |
List[Dict[str, float]] |
Kili polygon vertices. |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson polygon. |
Example
>>> vertices = [
{'x': 10.42, 'y': 27.12},
{'x': 1.53, 'y': 14.57},
{'x': 147.45, 'y': 14.12},
{'x': 14.23, 'y': 0.23}
]
>>> kili_polygon_to_geojson_polygon(vertices)
{
'type': 'Polygon',
'coordinates': [
[
[10.42, 27.12],
[1.53, 14.57],
[147.45, 14.12],
[14.23, 0.23],
[10.42, 27.12]
]
]
}
Source code in kili/utils/labels/geojson/polygon.py
def kili_polygon_to_geojson_polygon(vertices: List[Dict[str, float]]) -> Dict[str, Any]:
"""Convert a Kili polygon to a geojson polygon.
Args:
vertices: Kili polygon vertices.
Returns:
A geojson polygon.
!!! Example
```python
>>> vertices = [
{'x': 10.42, 'y': 27.12},
{'x': 1.53, 'y': 14.57},
{'x': 147.45, 'y': 14.12},
{'x': 14.23, 'y': 0.23}
]
>>> kili_polygon_to_geojson_polygon(vertices)
{
'type': 'Polygon',
'coordinates': [
[
[10.42, 27.12],
[1.53, 14.57],
[147.45, 14.12],
[14.23, 0.23],
[10.42, 27.12]
]
]
}
```
"""
polygon = [[vertex["x"], vertex["y"]] for vertex in vertices]
polygon.append(polygon[0]) # the first and last positions must be the same
return {"type": "Polygon", "coordinates": [polygon]}
Segmentation
Geojson segmentation utilities.
geojson_polygon_feature_to_kili_segmentation_annotation(polygon, categories=None, children=None, mid=None)
Convert a geojson polygon feature to a Kili segmentation annotation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
polygon |
Dict[str, Any] |
a geojson polygon feature. |
required |
categories |
Optional[List[Dict]] |
the categories of the annotation.
If not provided, the categories are taken from the |
None |
children |
Optional[Dict] |
the children of the annotation.
If not provided, the children are taken from the |
None |
mid |
Optional[str] |
the mid of the annotation.
If not provided, the mid is taken from the |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Kili segmentation annotation. |
Example
>>> polygon = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
...
],
[
...
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'semantic',
'job': 'job_name'
}
}
}
>>> geojson_polygon_feature_to_kili_segmentation_annotation(polygon)
{
'children': {},
'boundingPoly': [
{'normalizedVertices': [...]},
{'normalizedVertices': [...]}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'semantic'
}
Source code in kili/utils/labels/geojson/segmentation.py
def geojson_polygon_feature_to_kili_segmentation_annotation(
polygon: Dict[str, Any],
categories: Optional[List[Dict]] = None,
children: Optional[Dict] = None,
mid: Optional[str] = None,
) -> Dict[str, Any]:
# pylint: disable=line-too-long
"""Convert a geojson polygon feature to a Kili segmentation annotation.
Args:
polygon: a geojson polygon feature.
categories: the categories of the annotation.
If not provided, the categories are taken from the `kili` key of the geojson feature properties.
children: the children of the annotation.
If not provided, the children are taken from the `kili` key of the geojson feature properties.
mid: the mid of the annotation.
If not provided, the mid is taken from the `id` key of the geojson feature.
Returns:
A Kili segmentation annotation.
!!! Example
```python
>>> polygon = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
...
],
[
...
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'semantic',
'job': 'job_name'
}
}
}
>>> geojson_polygon_feature_to_kili_segmentation_annotation(polygon)
{
'children': {},
'boundingPoly': [
{'normalizedVertices': [...]},
{'normalizedVertices': [...]}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'semantic'
}
```
"""
assert (
polygon.get("type") == "Feature"
), f"Feature type must be `Feature`, got: {polygon['type']}"
assert (
polygon["geometry"]["type"] == "Polygon"
), f"Geometry type must be `Polygon`, got: {polygon['geometry']['type']}"
children = children or polygon["properties"].get("kili", {}).get("children", {})
categories = categories or polygon["properties"]["kili"]["categories"]
ret = {
"children": children,
"categories": categories,
"type": "semantic",
}
coords = polygon["geometry"]["coordinates"]
ret["boundingPoly"] = [
{"normalizedVertices": [{"x": coord[0], "y": coord[1]} for coord in polygon[:-1]]}
for polygon in coords
]
if mid is not None:
ret["mid"] = str(mid)
elif "id" in polygon:
ret["mid"] = str(polygon["id"])
return ret
kili_segmentation_annotation_to_geojson_polygon_feature(segmentation_annotation, job_name=None)
Convert a Kili segmentation annotation to a geojson polygon feature.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
segmentation_annotation |
Dict[str, Any] |
a Kili segmentation annotation. |
required |
job_name |
Optional[str] |
the name of the job to which the annotation belongs. |
None |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson polygon feature. |
Example
>>> segmentation = {
'children': {},
'boundingPoly': [
{'normalizedVertices': [...]},
{'normalizedVertices': [...]}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'semantic'
}
>>> kili_segmentation_annotation_to_geojson_polygon_feature(segmentation, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
...
],
[
...
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'semantic',
'job': 'job_name'
}
}
}
Source code in kili/utils/labels/geojson/segmentation.py
def kili_segmentation_annotation_to_geojson_polygon_feature(
segmentation_annotation: Dict[str, Any], job_name: Optional[str] = None
) -> Dict[str, Any]:
"""Convert a Kili segmentation annotation to a geojson polygon feature.
Args:
segmentation_annotation: a Kili segmentation annotation.
job_name: the name of the job to which the annotation belongs.
Returns:
A geojson polygon feature.
!!! Example
```python
>>> segmentation = {
'children': {},
'boundingPoly': [
{'normalizedVertices': [...]},
{'normalizedVertices': [...]}
],
'categories': [{'name': 'A'}],
'mid': 'mid_object',
'type': 'semantic'
}
>>> kili_segmentation_annotation_to_geojson_polygon_feature(segmentation, 'job_name')
{
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
...
],
[
...
]
]
},
'id': 'mid_object',
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'children': {},
'type': 'semantic',
'job': 'job_name'
}
}
}
```
"""
assert (
segmentation_annotation["type"] == "semantic"
), f"Annotation type must be `semantic`, got: {segmentation_annotation['type']}"
ret = {
"type": "Feature",
"geometry": kili_segmentation_to_geojson_polygon(segmentation_annotation["boundingPoly"]),
}
if "mid" in segmentation_annotation:
ret["id"] = segmentation_annotation["mid"]
ret["properties"] = {
"kili": {
k: v for k, v in segmentation_annotation.items() if k not in ["mid", "boundingPoly"]
}
}
if job_name is not None:
ret["properties"]["kili"]["job"] = job_name
return ret
kili_segmentation_to_geojson_polygon(bounding_poly)
Convert a Kili segmentation to a geojson polygon.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
bounding_poly |
List[Dict[str, List[Dict[str, Any]]]] |
a Kili segmentation bounding polygon. |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A geojson polygon. |
Example
>>> bounding_poly = [
{
'normalizedVertices': [...]
},
{
'normalizedVertices': [...]
}
]
>>> kili_segmentation_to_geojson_polygon(bounding_poly)
{
'type': 'Polygon',
'coordinates': [
[
...
],
[
...
]
]
}
Source code in kili/utils/labels/geojson/segmentation.py
def kili_segmentation_to_geojson_polygon(
bounding_poly: List[Dict[str, List[Dict[str, Any]]]]
) -> Dict[str, Any]:
"""Convert a Kili segmentation to a geojson polygon.
Args:
bounding_poly: a Kili segmentation bounding polygon.
Returns:
A geojson polygon.
!!! Example
```python
>>> bounding_poly = [
{
'normalizedVertices': [...]
},
{
'normalizedVertices': [...]
}
]
>>> kili_segmentation_to_geojson_polygon(bounding_poly)
{
'type': 'Polygon',
'coordinates': [
[
...
],
[
...
]
]
}
```
"""
ret = {"type": "Polygon", "coordinates": []}
for norm_vertices_dict in bounding_poly:
bbox = [[vertex["x"], vertex["y"]] for vertex in norm_vertices_dict["normalizedVertices"]]
bbox.append(bbox[0]) # the first and last positions must be the same
ret["coordinates"].append(bbox)
return ret
Collection
Geojson collection module.
features_to_feature_collection(features)
Convert a list of features to a feature collection.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
features |
Sequence[Dict] |
a list of Geojson features. |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Geojson feature collection. |
Example
>>> features = [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '1',
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '2',
}
}
]
>>> features_to_feature_collection(features)
{
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '1',
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '2',
}
}
]
}
Source code in kili/utils/labels/geojson/collection.py
def features_to_feature_collection(
features: Sequence[Dict],
) -> Dict[str, Any]:
"""Convert a list of features to a feature collection.
Args:
features: a list of Geojson features.
Returns:
A Geojson feature collection.
!!! Example
```python
>>> features = [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '1',
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '2',
}
}
]
>>> features_to_feature_collection(features)
{
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '1',
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-79.0, -3.0]},
'id': '2',
}
}
]
}
```
"""
return {"type": "FeatureCollection", "features": list(features)}
geojson_feature_collection_to_kili_json_response(feature_collection)
Convert a Geojson feature collection to a Kili label json response.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
feature_collection |
Dict[str, Any] |
a Geojson feature collection. |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Kili label json response. |
Warning
This method requires the kili
key to be present in the geojson features' properties.
In particular, the kili
dictionary of a feature must contain the categories
and type
of the annotation.
It must also contain the job
name.
Example
>>> feature_collection = {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
...
},
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'type': 'marker',
'job': 'POINT_DETECTION_JOB'
}
}
},
]
}
>>> geojson_feature_collection_to_kili_json_response(feature_collection)
{
'POINT_DETECTION_JOB': {
'annotations': [
{
'categories': [{'name': 'A'}],
'type': 'marker',
'point': ...
}
]
}
}
Source code in kili/utils/labels/geojson/collection.py
def geojson_feature_collection_to_kili_json_response(
feature_collection: Dict[str, Any]
) -> Dict[str, Any]:
# pylint: disable=line-too-long
"""Convert a Geojson feature collection to a Kili label json response.
Args:
feature_collection: a Geojson feature collection.
Returns:
A Kili label json response.
!!! Warning
This method requires the `kili` key to be present in the geojson features' properties.
In particular, the `kili` dictionary of a feature must contain the `categories` and `type` of the annotation.
It must also contain the `job` name.
!!! Example
```python
>>> feature_collection = {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
...
},
'properties': {
'kili': {
'categories': [{'name': 'A'}],
'type': 'marker',
'job': 'POINT_DETECTION_JOB'
}
}
},
]
}
>>> geojson_feature_collection_to_kili_json_response(feature_collection)
{
'POINT_DETECTION_JOB': {
'annotations': [
{
'categories': [{'name': 'A'}],
'type': 'marker',
'point': ...
}
]
}
}
```
"""
assert (
feature_collection["type"] == "FeatureCollection"
), f"Feature collection type must be `FeatureCollection`, got: {feature_collection['type']}"
annotation_tool_to_converter = {
"rectangle": geojson_polygon_feature_to_kili_bbox_annotation,
"marker": geojson_point_feature_to_kili_point_annotation,
"polygon": geojson_polygon_feature_to_kili_polygon_annotation,
"polyline": geojson_linestring_feature_to_kili_line_annotation,
"semantic": geojson_polygon_feature_to_kili_segmentation_annotation,
}
json_response = {}
for feature in feature_collection["features"]:
if feature.get("properties").get("kili", {}).get("job") is None:
raise ValueError(f"Job name is missing in the GeoJson feature {feature}")
job_name = feature["properties"]["kili"]["job"]
if feature.get("properties").get("kili", {}).get("type") is None:
raise ValueError(f"Annotation `type` is missing in the GeoJson feature {feature}")
annotation_tool = feature["properties"]["kili"]["type"]
if annotation_tool not in annotation_tool_to_converter:
raise ValueError(f"Annotation tool {annotation_tool} is not supported.")
kili_annotation = annotation_tool_to_converter[annotation_tool](feature)
if job_name not in json_response:
json_response[job_name] = {}
if "annotations" not in json_response[job_name]:
json_response[job_name]["annotations"] = []
json_response[job_name]["annotations"].append(kili_annotation)
return json_response
kili_json_response_to_feature_collection(json_response)
Convert a Kili label json response to a Geojson feature collection.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
json_response |
Dict[str, Any] |
a Kili label json response. |
required |
Returns:
Type | Description |
---|---|
Dict[str, Any] |
A Geojson feature collection. |
Example
>>> json_response = {
'job_1': {
'annotations': [...]
},
'job_2': {
'annotations': [...]
}
}
>>> kili_json_response_to_feature_collection(json_response)
{
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
...
}
},
{
'type': 'Feature',
'geometry': {
...
}
}
]
}
Source code in kili/utils/labels/geojson/collection.py
def kili_json_response_to_feature_collection(json_response: Dict[str, Any]) -> Dict[str, Any]:
"""Convert a Kili label json response to a Geojson feature collection.
Args:
json_response: a Kili label json response.
Returns:
A Geojson feature collection.
!!! Example
```python
>>> json_response = {
'job_1': {
'annotations': [...]
},
'job_2': {
'annotations': [...]
}
}
>>> kili_json_response_to_feature_collection(json_response)
{
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
...
}
},
{
'type': 'Feature',
'geometry': {
...
}
}
]
}
```
"""
features = []
annotation_tool_to_converter = {
"rectangle": kili_bbox_annotation_to_geojson_polygon_feature,
"marker": kili_point_annotation_to_geojson_point_feature,
"polygon": kili_polygon_annotation_to_geojson_polygon_feature,
"polyline": kili_line_annotation_to_geojson_linestring_feature,
"semantic": kili_segmentation_annotation_to_geojson_polygon_feature,
}
jobs_skipped = []
ann_tools_not_supported = set()
for job_name, job_response in json_response.items():
if "annotations" not in job_response:
jobs_skipped.append(job_name)
continue
for ann in job_response["annotations"]:
annotation_tool = ann.get("type")
if annotation_tool not in annotation_tool_to_converter:
ann_tools_not_supported.add(annotation_tool)
continue
converter = annotation_tool_to_converter[annotation_tool]
feature = converter(ann, job_name=job_name)
features.append(feature)
if jobs_skipped:
warnings.warn(f"Jobs {jobs_skipped} cannot be exported to GeoJson format.", stacklevel=2)
if ann_tools_not_supported:
warnings.warn(
f"Annotation tools {ann_tools_not_supported} are not supported and will be skipped.",
stacklevel=2,
)
return features_to_feature_collection(features)