Skip to content

Commit 3175723

Browse files
authored
Merge pull request #22 from kili-technology/feature/lab-3861-support-multipoint-multilinestring-geometrycollection
feat(LAB-3861): support `MultiPoint`, `MultiLineString` & `GeometryCollection` geojson geometry types
2 parents bb97c63 + f05173e commit 3175723

File tree

7 files changed

+509
-11
lines changed

7 files changed

+509
-11
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "kili-formats"
3-
version = "0.2.8"
3+
version = "0.2.9"
44
description = ""
55
authors = [{ name = "Kili Technology", email = "contact@kili-technology.com" }]
66
license = { file = "LICENSE.txt" }

src/kili_formats/format/geojson/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
geojson_feature_collection_to_kili_json_response,
1111
kili_json_response_to_feature_collection,
1212
)
13+
from .geometrycollection import geojson_geometrycollection_feature_to_kili_annotations
1314
from .line import (
1415
geojson_linestring_feature_to_kili_line_annotation,
1516
kili_line_annotation_to_geojson_linestring_feature,
1617
kili_line_to_geojson_linestring,
1718
)
19+
from .multilinestring import geojson_multilinestring_feature_to_kili_line_annotations
20+
from .multipoint import geojson_multipoint_feature_to_kili_point_annotations
1821
from .point import (
1922
geojson_point_feature_to_kili_point_annotation,
2023
kili_point_annotation_to_geojson_point_feature,
@@ -43,6 +46,9 @@ def convert_from_kili_to_geojson_format(response: Dict[str, Any]):
4346
"geojson_polygon_feature_to_kili_segmentation_annotation",
4447
"geojson_point_feature_to_kili_point_annotation",
4548
"geojson_linestring_feature_to_kili_line_annotation",
49+
"geojson_multipoint_feature_to_kili_point_annotations",
50+
"geojson_multilinestring_feature_to_kili_line_annotations",
51+
"geojson_geometrycollection_feature_to_kili_annotations",
4652
"kili_bbox_annotation_to_geojson_polygon_feature",
4753
"kili_bbox_to_geojson_polygon",
4854
"kili_line_annotation_to_geojson_linestring_feature",

src/kili_formats/format/geojson/collection.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
kili_classification_annotation_to_geojson_non_localised_feature,
1313
)
1414
from .exceptions import ConversionError
15+
from .geometrycollection import geojson_geometrycollection_feature_to_kili_annotations
1516
from .line import (
1617
geojson_linestring_feature_to_kili_line_annotation,
1718
kili_line_annotation_to_geojson_linestring_feature,
1819
)
20+
from .multilinestring import geojson_multilinestring_feature_to_kili_line_annotations
21+
from .multipoint import geojson_multipoint_feature_to_kili_point_annotations
1922
from .point import (
2023
geojson_point_feature_to_kili_point_annotation,
2124
kili_point_annotation_to_geojson_point_feature,
@@ -383,24 +386,33 @@ def geojson_feature_collection_to_kili_json_response(
383386
raise ValueError("Invalid kili property in non localised feature")
384387
continue
385388

386-
if feature.get("properties").get("kili", {}).get("type") is None:
387-
raise ValueError(f"Annotation `type` is missing in the GeoJson feature {feature}")
389+
geometry_type = feature["geometry"]["type"]
388390

389-
annotation_tool = feature["properties"]["kili"]["type"]
391+
if geometry_type == "GeometryCollection":
392+
kili_annotations = geojson_geometrycollection_feature_to_kili_annotations(feature)
393+
elif geometry_type == "MultiPoint":
394+
kili_annotations = geojson_multipoint_feature_to_kili_point_annotations(feature)
395+
elif geometry_type == "MultiLineString":
396+
kili_annotations = geojson_multilinestring_feature_to_kili_line_annotations(feature)
397+
else:
398+
if feature.get("properties").get("kili", {}).get("type") is None:
399+
raise ValueError(f"Annotation `type` is missing in the GeoJson feature {feature}")
390400

391-
if annotation_tool not in annotation_tool_to_converter:
392-
raise ValueError(f"Annotation tool {annotation_tool} is not supported.")
401+
annotation_tool = feature["properties"]["kili"]["type"]
393402

394-
kili_annotation = annotation_tool_to_converter[annotation_tool](feature)
403+
if annotation_tool not in annotation_tool_to_converter:
404+
raise ValueError(f"Annotation tool {annotation_tool} is not supported.")
405+
406+
kili_annotation = annotation_tool_to_converter[annotation_tool](feature)
407+
kili_annotations = (
408+
kili_annotation if isinstance(kili_annotation, list) else [kili_annotation]
409+
)
395410

396411
if job_name not in json_response:
397412
json_response[job_name] = {}
398413
if "annotations" not in json_response[job_name]:
399414
json_response[job_name]["annotations"] = []
400415

401-
if isinstance(kili_annotation, list):
402-
json_response[job_name]["annotations"].extend(kili_annotation)
403-
else:
404-
json_response[job_name]["annotations"].append(kili_annotation)
416+
json_response[job_name]["annotations"].extend(kili_annotations)
405417

406418
return json_response
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"""Geometry collection conversion functions between Kili and geojson formats."""
2+
3+
import uuid
4+
from typing import Any, Dict, List, Optional
5+
6+
from .line import geojson_linestring_feature_to_kili_line_annotation
7+
from .multilinestring import geojson_multilinestring_feature_to_kili_line_annotations
8+
from .multipoint import geojson_multipoint_feature_to_kili_point_annotations
9+
from .point import geojson_point_feature_to_kili_point_annotation
10+
from .polygon import geojson_polygon_feature_to_kili_polygon_annotation
11+
from .segmentation import geojson_polygon_feature_to_kili_segmentation_annotation
12+
13+
14+
def geojson_geometrycollection_feature_to_kili_annotations(
15+
geometrycollection: Dict[str, Any],
16+
categories: Optional[List[Dict]] = None,
17+
children: Optional[Dict] = None,
18+
mid: Optional[str] = None,
19+
) -> List[Dict[str, Any]]:
20+
"""Convert a geojson geometry collection feature to Kili annotations.
21+
22+
Processes each geometry in the collection and converts it to the appropriate Kili annotation type.
23+
Points become marker annotations, LineStrings become polyline annotations,
24+
Polygons become polygon or semantic annotations depending on the type specified,
25+
MultiPoint and MultiLineString geometries are expanded into multiple annotations.
26+
27+
Args:
28+
geometrycollection: A geojson geometry collection feature.
29+
categories: The categories of the annotations.
30+
If not provided, the categories are taken from the `kili` key of the geojson feature properties.
31+
children: The children of the annotations.
32+
If not provided, the children are taken from the `kili` key of the geojson feature properties.
33+
mid: The mid of the annotations.
34+
If not provided, the mid is taken from the `id` key of the geojson feature.
35+
If no id is available, a new UUID is generated.
36+
37+
Returns:
38+
A list of Kili annotations corresponding to each geometry in the collection.
39+
40+
!!! Example
41+
```python
42+
>>> geometrycollection = {
43+
'type': 'Feature',
44+
'geometry': {
45+
'type': 'GeometryCollection',
46+
'geometries': [
47+
{
48+
'type': 'Point',
49+
'coordinates': [1.0, 2.0]
50+
},
51+
{
52+
'type': 'LineString',
53+
'coordinates': [[3.0, 4.0], [5.0, 6.0]]
54+
},
55+
{
56+
'type': 'Polygon',
57+
'coordinates': [[[7.0, 8.0], [9.0, 8.0], [9.0, 10.0], [7.0, 10.0], [7.0, 8.0]]]
58+
}
59+
]
60+
},
61+
'id': 'complex_001',
62+
'properties': {
63+
'kili': {
64+
'categories': [{'name': 'complex'}],
65+
'children': {}
66+
}
67+
}
68+
}
69+
>>> geojson_geometrycollection_feature_to_kili_annotations(geometrycollection)
70+
[
71+
{
72+
'children': {},
73+
'point': {'x': 1.0, 'y': 2.0},
74+
'categories': [{'name': 'complex'}],
75+
'mid': 'complex_001',
76+
'type': 'marker'
77+
},
78+
{
79+
'children': {},
80+
'polyline': [{'x': 3.0, 'y': 4.0}, {'x': 5.0, 'y': 6.0}],
81+
'categories': [{'name': 'complex'}],
82+
'mid': 'complex_001',
83+
'type': 'polyline'
84+
},
85+
{
86+
'children': {},
87+
'boundingPoly': [{'normalizedVertices': [{'x': 7.0, 'y': 8.0}, {'x': 9.0, 'y': 8.0}, {'x': 9.0, 'y': 10.0}, {'x': 7.0, 'y': 10.0}]}],
88+
'categories': [{'name': 'complex'}],
89+
'mid': 'complex_001',
90+
'type': 'polygon'
91+
}
92+
]
93+
```
94+
"""
95+
96+
assert (
97+
geometrycollection.get("type") == "Feature"
98+
), f"Feature type must be `Feature`, got: {geometrycollection['type']}"
99+
assert (
100+
geometrycollection["geometry"]["type"] == "GeometryCollection"
101+
), f"Geometry type must be `GeometryCollection`, got: {geometrycollection['geometry']['type']}"
102+
103+
children = children or geometrycollection["properties"].get("kili", {}).get("children", {})
104+
categories = categories or geometrycollection["properties"]["kili"]["categories"]
105+
106+
kili_properties = geometrycollection["properties"].get("kili", {})
107+
annotation_type = kili_properties.get("type")
108+
109+
annotation_mid = None
110+
if mid is not None:
111+
annotation_mid = str(mid)
112+
elif "id" in geometrycollection:
113+
annotation_mid = str(geometrycollection["id"])
114+
else:
115+
annotation_mid = str(uuid.uuid4())
116+
117+
geometries = geometrycollection["geometry"]["geometries"]
118+
annotations = []
119+
120+
for geometry in geometries:
121+
feature = {"type": "Feature", "geometry": geometry, "properties": {"kili": kili_properties}}
122+
123+
if geometry["type"] == "Point":
124+
if annotation_type and annotation_type != "marker":
125+
continue
126+
ann = geojson_point_feature_to_kili_point_annotation(
127+
feature, categories=categories, children=children, mid=annotation_mid
128+
)
129+
annotations.append(ann)
130+
131+
elif geometry["type"] == "LineString":
132+
if annotation_type and annotation_type != "polyline":
133+
continue
134+
ann = geojson_linestring_feature_to_kili_line_annotation(
135+
feature, categories=categories, children=children, mid=annotation_mid
136+
)
137+
annotations.append(ann)
138+
139+
elif geometry["type"] == "Polygon":
140+
if annotation_type:
141+
if annotation_type == "polygon":
142+
ann = geojson_polygon_feature_to_kili_polygon_annotation(
143+
feature, categories=categories, children=children, mid=annotation_mid
144+
)
145+
annotations.append(ann)
146+
elif annotation_type == "semantic":
147+
anns = geojson_polygon_feature_to_kili_segmentation_annotation(
148+
feature, categories=categories, children=children, mid=annotation_mid
149+
)
150+
annotations.extend(anns)
151+
else:
152+
ann = geojson_polygon_feature_to_kili_polygon_annotation(
153+
feature, categories=categories, children=children, mid=annotation_mid
154+
)
155+
annotations.append(ann)
156+
157+
elif geometry["type"] == "MultiPoint":
158+
if annotation_type and annotation_type != "marker":
159+
continue
160+
anns = geojson_multipoint_feature_to_kili_point_annotations(
161+
feature, categories=categories, children=children
162+
)
163+
annotations.extend(anns)
164+
165+
elif geometry["type"] == "MultiLineString":
166+
if annotation_type and annotation_type != "polyline":
167+
continue
168+
anns = geojson_multilinestring_feature_to_kili_line_annotations(
169+
feature, categories=categories, children=children
170+
)
171+
annotations.extend(anns)
172+
173+
elif geometry["type"] == "MultiPolygon":
174+
if annotation_type and annotation_type != "semantic":
175+
continue
176+
anns = geojson_polygon_feature_to_kili_segmentation_annotation(
177+
feature, categories=categories, children=children, mid=annotation_mid
178+
)
179+
annotations.extend(anns)
180+
181+
return annotations
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Multi-linestring conversion functions between Kili and geojson formats."""
2+
3+
import uuid
4+
from typing import Any, Dict, List, Optional
5+
6+
7+
def geojson_multilinestring_feature_to_kili_line_annotations(
8+
multilinestring: Dict[str, Any],
9+
categories: Optional[List[Dict]] = None,
10+
children: Optional[Dict] = None,
11+
) -> List[Dict[str, Any]]:
12+
"""Convert a geojson multi-linestring feature to multiple Kili line annotations.
13+
14+
Each linestring in the multi-linestring geometry is converted to a separate Kili polyline annotation.
15+
All resulting annotations share the same categories and children but have unique mids.
16+
17+
Args:
18+
multilinestring: A geojson multi-linestring feature.
19+
categories: The categories of the annotations.
20+
If not provided, the categories are taken from the `kili` key of the geojson feature properties.
21+
children: The children of the annotations.
22+
If not provided, the children are taken from the `kili` key of the geojson feature properties.
23+
24+
Returns:
25+
A list of Kili polyline annotations, one for each linestring in the multi-linestring.
26+
27+
!!! Example
28+
```python
29+
>>> multilinestring = {
30+
'type': 'Feature',
31+
'geometry': {
32+
'type': 'MultiLineString',
33+
'coordinates': [
34+
[[1.0, 2.0], [3.0, 4.0]],
35+
[[5.0, 6.0], [7.0, 8.0], [9.0, 10.0]]
36+
]
37+
},
38+
'properties': {
39+
'kili': {
40+
'categories': [{'name': 'road'}],
41+
'children': {}
42+
}
43+
}
44+
}
45+
>>> geojson_multilinestring_feature_to_kili_line_annotations(multilinestring)
46+
[
47+
{
48+
'children': {},
49+
'categories': [{'name': 'road'}],
50+
'type': 'polyline',
51+
'polyline': [{'x': 1.0, 'y': 2.0}, {'x': 3.0, 'y': 4.0}],
52+
'mid': 'generated-uuid-1'
53+
},
54+
{
55+
'children': {},
56+
'categories': [{'name': 'road'}],
57+
'type': 'polyline',
58+
'polyline': [{'x': 5.0, 'y': 6.0}, {'x': 7.0, 'y': 8.0}, {'x': 9.0, 'y': 10.0}],
59+
'mid': 'generated-uuid-2'
60+
}
61+
]
62+
```
63+
"""
64+
65+
assert (
66+
multilinestring.get("type") == "Feature"
67+
), f"Feature type must be `Feature`, got: {multilinestring['type']}"
68+
assert (
69+
multilinestring["geometry"]["type"] == "MultiLineString"
70+
), f"Geometry type must be `MultiLineString`, got: {multilinestring['geometry']['type']}"
71+
72+
children = children or multilinestring["properties"].get("kili", {}).get("children", {})
73+
categories = categories or multilinestring["properties"]["kili"]["categories"]
74+
75+
coords = multilinestring["geometry"]["coordinates"]
76+
annotations = []
77+
78+
for line_coords in coords:
79+
ret = {
80+
"children": children,
81+
"categories": categories,
82+
"type": "polyline",
83+
"polyline": [{"x": coord[0], "y": coord[1]} for coord in line_coords],
84+
"mid": str(uuid.uuid4()),
85+
}
86+
annotations.append(ret)
87+
88+
return annotations

0 commit comments

Comments
 (0)