Skip to content

Commit 6f14758

Browse files
committed
fixed voc convertor
1 parent 786eb9c commit 6f14758

File tree

22 files changed

+1094
-87
lines changed

22 files changed

+1094
-87
lines changed

requirements_dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
superannotate_schemas>=v1.0.43dev1
1+
superannotate_schemas>=v1.0.43dev3
22

src/superannotate/lib/app/input_converters/converters/voc_converters/voc_helper.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,14 @@ def _get_image_shape_from_xml(file_path):
4747
height = int(size.find("height").text)
4848

4949
return height, width
50+
51+
52+
def _get_image_metadata(file_path):
53+
with open(os.path.splitext(file_path)[0] + ".xml") as f:
54+
tree = ET.parse(f)
55+
56+
size = tree.find("size")
57+
width = int(size.find("width").text)
58+
height = int(size.find("height").text)
59+
60+
return tree.find("filename").text, height, width

src/superannotate/lib/app/input_converters/converters/voc_converters/voc_to_sa_vector.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ....common import write_to_json
1212
from ..sa_json_helper import _create_sa_json
1313
from ..sa_json_helper import _create_vector_instance
14-
from .voc_helper import _get_image_shape_from_xml
14+
from .voc_helper import _get_image_metadata
1515
from .voc_helper import _get_voc_instances_from_xml
1616
from .voc_helper import _iou
1717

@@ -118,11 +118,11 @@ def voc_instance_segmentation_to_sa_vector(voc_root, output_dir):
118118
sa_instances.append(sa_obj)
119119

120120
images_converted.append(filename)
121-
file_name = "%s.jpg___objects.json" % filename.stem
122-
height, width = _get_image_shape_from_xml(annotation_dir / filename.name)
123-
sa_metadata = {"name": filename.stem, "height": height, "width": width}
121+
file_name, height, width = _get_image_metadata(annotation_dir / filename.name)
122+
file_path = f"{file_name}___objects.json"
123+
sa_metadata = {"name": str(filename), "height": height, "width": width}
124124
sa_json = _create_sa_json(sa_instances, sa_metadata)
125-
write_to_json(output_dir / file_name, sa_json)
125+
write_to_json(output_dir / file_path, sa_json)
126126

127127
finish_event.set()
128128
tqdm_thread.join()
@@ -161,11 +161,11 @@ def voc_object_detection_to_sa_vector(voc_root, output_dir):
161161
sa_instances.append(sa_obj)
162162

163163
images_converted.append(filename)
164-
file_name = "%s.jpg___objects.json" % filename.stem
165-
height, width = _get_image_shape_from_xml(annotation_dir / filename.name)
166-
sa_metadata = {"name": filename.stem, "height": height, "width": width}
164+
file_name, height, width = _get_image_metadata(annotation_dir / filename.name)
165+
file_path = f"{file_name}___objects.json"
166+
sa_metadata = {"name": str(filename), "height": height, "width": width}
167167
sa_json = _create_sa_json(sa_instances, sa_metadata)
168-
write_to_json(output_dir / file_name, sa_json)
168+
write_to_json(output_dir / file_path, sa_json)
169169

170170
finish_event.set()
171171
tqdm_thread.join()

src/superannotate/lib/core/video_convertor.py

Lines changed: 75 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
from typing import List
77
from typing import Optional
88

9+
from lib.core.enums import AnnotationTypes
10+
from lib.core.exceptions import AppException
911
from pydantic import BaseModel
1012

1113

1214
class Annotation(BaseModel):
1315
instanceId: int
1416
type: str
15-
className: str
17+
className: Optional[str]
18+
x: Optional[Any]
19+
y: Optional[Any]
1620
points: Optional[Dict]
1721
attributes: Optional[List[Any]] = []
1822
keyframe: bool = False
@@ -25,6 +29,7 @@ class FrameAnnotation(BaseModel):
2529

2630
class VideoFrameGenerator:
2731
def __init__(self, annotation_data: dict, fps: int):
32+
self.validate_annotations(annotation_data)
2833
self.id_generator = iter(itertools.count(0))
2934
self._annotation_data = annotation_data
3035
self.duration = annotation_data["metadata"]["duration"] / (1000 * 1000)
@@ -36,40 +41,54 @@ def __init__(self, annotation_data: dict, fps: int):
3641
self._mapping = {}
3742
self._process()
3843

44+
@staticmethod
45+
def validate_annotations(annotation_data: dict):
46+
try:
47+
annotation_data["metadata"]["duration"]
48+
except KeyError:
49+
raise AppException("Video not annotated yet")
50+
3951
def get_frame(self, frame_no: int):
4052
try:
4153
return self.annotations[frame_no]
4254
except KeyError:
4355
self.annotations[frame_no] = FrameAnnotation(frame=frame_no)
4456
return self.annotations[frame_no]
4557

46-
def interpolate_annotations(
47-
self,
48-
class_name: str,
49-
from_frame: int,
50-
to_frame: int,
51-
data: dict,
52-
instance_id: int,
53-
steps: dict = None,
54-
annotation_type: str = "bbox",
58+
def _interpolate(
59+
self,
60+
class_name: str,
61+
from_frame: int,
62+
to_frame: int,
63+
data: dict,
64+
instance_id: int,
65+
steps: dict = None,
66+
annotation_type: str = "bbox",
5567
) -> dict:
5668
annotations = {}
5769
for idx, frame_idx in enumerate(range(from_frame + 1, to_frame), 1):
58-
points = None
59-
if annotation_type == "bbox" and data.get("points") and steps:
60-
points = {
70+
tmp_data = {}
71+
if annotation_type == AnnotationTypes.BBOX and data.get("points") and steps:
72+
tmp_data["points"] = {
6173
"x1": round(data["points"]["x1"] + steps["x1"] * idx, 2),
6274
"y1": round(data["points"]["y1"] + steps["y1"] * idx, 2),
6375
"x2": round(data["points"]["x2"] + steps["x2"] * idx, 2),
6476
"y2": round(data["points"]["y2"] + steps["y2"] * idx, 2),
6577
}
78+
elif annotation_type == AnnotationTypes.POINT:
79+
tmp_data = {
80+
"x": round(data["x"] + steps["x"] * idx, 2),
81+
"y": round(data["y"] + steps["y"] * idx, 2)
82+
}
83+
elif annotation_type in (AnnotationTypes.POLYGON, AnnotationTypes.POLYLINE):
84+
tmp_data["points"] = [point + steps[idx] * 2 for idx, point in enumerate(data["points"])]
6685
annotations[frame_idx] = Annotation(
6786
instanceId=instance_id,
6887
type=annotation_type,
6988
className=class_name,
70-
points=points,
7189
attributes=data["attributes"],
7290
keyframe=False,
91+
**tmp_data
7392
)
7493
return annotations
7594

@@ -98,6 +117,9 @@ def get_median(self, annotations: List[dict]) -> dict:
98117
median_annotation = annotation
99118
return median_annotation
100119

120+
def calculate_sped(self, from_frame, to_frame):
121+
pass
122+
101123
@staticmethod
102124
def merge_first_frame(frames_mapping):
103125
try:
@@ -108,11 +130,37 @@ def merge_first_frame(frames_mapping):
108130
finally:
109131
return frames_mapping
110132

133+
def _interpolate_frames(
134+
self, from_frame, from_frame_no, to_frame, to_frame_no, annotation_type, class_name, instance_id
135+
):
136+
steps = None
137+
frames_diff = to_frame_no - from_frame_no
138+
if annotation_type == AnnotationTypes.BBOX and from_frame.get("points") and to_frame.get("points"):
139+
steps = {}
140+
for point in "x1", "x2", "y1", "y2":
141+
steps[point] = round(
142+
(to_frame["points"][point] - from_frame["points"][point]) / frames_diff, 2
143+
)
144+
elif annotation_type == AnnotationTypes.POINT:
145+
steps = {
146+
"x": (to_frame["x"] - from_frame["x"]) / frames_diff,
147+
"y": (to_frame["y"] - from_frame["y"]) / frames_diff
148+
}
149+
elif annotation_type in (AnnotationTypes.POLYGON, AnnotationTypes.POLYLINE):
150+
steps = [
151+
(to_point - from_point) / frames_diff
152+
for from_point, to_point in zip(from_frame["points"], to_frame["points"])
153+
]
154+
return self._interpolate(
155+
class_name=class_name, from_frame=from_frame_no, to_frame=to_frame_no, data=from_frame,
156+
instance_id=instance_id, steps=steps, annotation_type=annotation_type
157+
)
158+
111159
def _process(self):
112160
for instance in self._annotation_data["instances"]:
113161
instance_id = next(self.id_generator)
114162
annotation_type = instance["meta"]["type"]
115-
class_name = instance["meta"]["className"]
163+
class_name = instance["meta"].get("className")
116164
for parameter in instance["parameters"]:
117165
frames_mapping = defaultdict(list)
118166
last_frame_no = None
@@ -131,55 +179,15 @@ def _process(self):
131179
)
132180
frames_diff = to_frame_no - from_frame_no
133181
if frames_diff > 1:
134-
steps = None
135-
if (
136-
annotation_type == "bbox"
137-
and from_frame.get("points")
138-
and to_frame.get("points")
139-
):
140-
steps = {
141-
"y1": round(
142-
(
143-
to_frame["points"]["y1"]
144-
- from_frame["points"]["y1"]
145-
)
146-
/ frames_diff,
147-
2,
148-
),
149-
"x2": round(
150-
(
151-
to_frame["points"]["x2"]
152-
- from_frame["points"]["x2"]
153-
)
154-
/ frames_diff,
155-
2,
156-
),
157-
"x1": round(
158-
(
159-
to_frame["points"]["x1"]
160-
- from_frame["points"]["x1"]
161-
)
162-
/ frames_diff,
163-
2,
164-
),
165-
"y2": round(
166-
(
167-
to_frame["points"]["y2"]
168-
- from_frame["points"]["y2"]
169-
)
170-
/ frames_diff,
171-
2,
172-
),
173-
}
174182
interpolated_frames.update(
175-
self.interpolate_annotations(
183+
self._interpolate_frames(
184+
from_frame=from_frame,
185+
from_frame_no=from_frame_no,
186+
to_frame=to_frame,
187+
to_frame_no=to_frame_no,
176188
class_name=class_name,
177-
from_frame=from_frame_no,
178-
to_frame=to_frame_no,
179-
data=from_frame,
180-
instance_id=instance_id,
181-
steps=steps,
182189
annotation_type=annotation_type,
190+
instance_id=instance_id
183191
)
184192
)
185193
start_median_frame = self.get_median(frames_mapping[from_frame_no])
@@ -188,6 +196,8 @@ def _process(self):
188196
instanceId=instance_id,
189197
type=annotation_type,
190198
className=class_name,
199+
x=start_median_frame.get("x"),
200+
y=start_median_frame.get("y"),
191201
points=start_median_frame.get("points"),
192202
attributes=start_median_frame["attributes"],
193203
keyframe=True,
@@ -196,6 +206,8 @@ def _process(self):
196206
instanceId=instance_id,
197207
type=annotation_type,
198208
className=class_name,
209+
x=start_median_frame.get("x"),
210+
y=start_median_frame.get("y"),
199211
points=end_median_frame.get("points"),
200212
attributes=end_median_frame["attributes"],
201213
keyframe=True,
@@ -208,4 +220,5 @@ def _process(self):
208220

209221
def __iter__(self):
210222
for frame_no in range(1, int(self.frames_count) + 1):
211-
yield self.get_frame(frame_no).dict()
223+
frame = self.get_frame(frame_no)
224+
yield {**frame.dict(exclude_unset=True), **frame.dict(exclude_none=True)}

src/superannotate/lib/infrastructure/controller.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,18 +1174,15 @@ def benchmark(
11741174
if export_response.errors:
11751175
return export_response
11761176

1177-
download_use_case = usecases.DownloadExportUseCase(
1177+
usecases.DownloadExportUseCase(
11781178
service=self._backend_client,
11791179
project=project,
11801180
export_name=export_response.data["name"],
11811181
folder_path=export_root,
11821182
extract_zip_contents=True,
11831183
to_s3_bucket=False,
11841184
reporter=self.default_reporter,
1185-
)
1186-
if download_use_case.is_valid():
1187-
for _ in download_use_case.execute():
1188-
pass
1185+
).execute()
11891186

11901187
use_case = usecases.BenchmarkUseCase(
11911188
project=project,

src/superannotate/lib/infrastructure/services.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import datetime
33
import json
4+
import platform
45
import time
56
from contextlib import contextmanager
67
from typing import Callable
@@ -24,6 +25,7 @@
2425
from lib.infrastructure.helpers import timed_lru_cache
2526
from lib.infrastructure.stream_data_handler import StreamedAnnotations
2627
from requests.exceptions import HTTPError
28+
from superannotate.version import __version__
2729

2830
requests.packages.urllib3.disable_warnings()
2931

@@ -83,7 +85,8 @@ def default_headers(self):
8385
"Authorization": self._auth_token,
8486
"authtype": self.AUTH_TYPE,
8587
"Content-Type": "application/json",
86-
# "User-Agent": constance.__version__,
88+
"User-Agent": f"Python-SDK-Version: {__version__}; Python: {platform.python_version()}; "
89+
f"OS: {platform.system()}; Team: {self.team_id}",
8790
}
8891

8992
@property

src/superannotate/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.3.5dev1"
1+
__version__ = "4.3.5dev2"

tests/convertors/test_voc.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import os
2+
from os.path import dirname
13
from pathlib import Path
24

35
import pytest
46
import superannotate as sa
7+
from tests import DATA_SET_PATH
58

69

710
@pytest.mark.skip(reason="Need to adjust")
@@ -10,7 +13,7 @@ def test_voc_vector_instance(tmpdir):
1013

1114
input_dir = (
1215
Path("tests")
13-
/ "converter_test"
16+
/ f"{DATA_SET_PATH}/converter_test"
1417
/ "VOC"
1518
/ "input"
1619
/ "fromPascalVOCToSuperAnnotate"

0 commit comments

Comments
 (0)