Skip to content

Commit 95c2e11

Browse files
committed
comment annotation
1 parent 67a2550 commit 95c2e11

File tree

9 files changed

+89
-43
lines changed

9 files changed

+89
-43
lines changed

docs/source/tutorial.sdk.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ SuperAnnotate format annotations:
359359
df = sa.aggregate_annotations_as_df("<path_to_project_folder>")
360360
361361
The created DataFrame will have columns :code:`imageName`, :code:`instanceId`,
362-
:code:`className`, :code:`attributeGroupName`, :code:`attributeName`, :code:`type`, :code:`error`, :code:`locked`, :code:`visible`, :code:`trackingId`, :code:`probability`, :code:`pointLabels`, :code:`meta` (geometry information as string), :code:`commentResolved`, :code:`classColor`.
362+
:code:`className`, :code:`attributeGroupName`, :code:`attributeName`, :code:`type`, :code:`error`, :code:`locked`, :code:`visible`, :code:`trackingId`, :code:`probability`, :code:`pointLabels`, :code:`meta` (geometry information as string), :code:`commentResolved`, :code:`classColor`, :code:`groupId`.
363363

364364
Example of created DataFrame:
365365

superannotate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
image_path_to_annotation_paths, project_type_int_to_str,
2222
project_type_str_to_int, user_role_str_to_int
2323
)
24+
from .dataframe_filtering import filter_annotation_instances, filter_comments
2425
from .db.annotation_classes import (
2526
create_annotation_class, create_annotation_classes_from_classes_json,
2627
delete_annotation_class, download_annotation_classes_json,
@@ -62,7 +63,6 @@
6263
convert_platform, convert_project_type, export_annotation_format,
6364
import_annotation_format
6465
)
65-
from .instance_filtering import filter_annotation_instances
6666
from .version import __version__
6767

6868
formatter = logging.Formatter(fmt='SA-PYTHON-SDK - %(levelname)s - %(message)s')

superannotate/analytics/common.py

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@
99

1010
def df_to_annotations(df, output_dir):
1111
"""Converts and saves pandas DataFrame annotation info (see aggregate_annotations_as_df) in output_dir
12-
The DataFrame should have columns: "imageName", "classNmae", "attributeGroupName", "attributeName", "type", "error", "locked", "visible", trackingId", "probability", "pointLabels", "meta", "commentResolved", "classColor"
12+
The DataFrame should have columns: "imageName", "classNmae", "attributeGroupName", "attributeName", "type", "error", "locked", "visible", trackingId", "probability", "pointLabels", "meta", "commentResolved", "classColor", "groupId"
1313
1414
Currently only works for Vector projects.
1515
16-
:param df: pandas DataFrame
16+
:param df: pandas DataFrame of annotations possibly created by aggregate_annotations_as_df
1717
:type df: pandas.DataFrame
18-
:param include_classes_wo_annotations: enables inclusion of classes without annotations info
19-
:type include_classes_wo_annotations: bool
18+
:param output_dir: output dir for annotations and classes.json
19+
:type output_dir: str or Pathlike
2020
21-
:return: DataFrame on annotations with columns: ["imageName", "classNmae", "attributeGroupName", "attributeName", "type", "error", "locked", "visible", trackingId", "probability", "pointLabels", "meta", "commentResolved"]
22-
:rtype: pandas DataFrame
2321
"""
2422

2523
project_suffix = "objects.json"
@@ -50,6 +48,7 @@ def df_to_annotations(df, output_dir):
5048
)
5149
instance_annotation["trackingId"] = instance_df.iloc[0]["trackingId"
5250
]
51+
instance_annotation["groupId"] = int(instance_df.iloc[0]["groupId"])
5352
instance_annotation.update(annotation_meta)
5453
for _, row in instance_df.iterrows():
5554
if row["attributeGroupName"] is not None:
@@ -60,6 +59,14 @@ def df_to_annotations(df, output_dir):
6059
}
6160
)
6261
image_annotation.append(instance_annotation)
62+
63+
comments = image_df[image_df["type"] == "comment"]
64+
for _, comment in comments.iterrows():
65+
comment_json = {"type": "comment"}
66+
comment_json.update(comment["meta"])
67+
comment_json["resolved"] = comment["commentResolved"]
68+
image_annotation.append(comment_json)
69+
6370
json.dump(
6471
image_annotation,
6572
open(output_dir / f"{image}___{project_suffix}", "w"),
@@ -124,7 +131,7 @@ def aggregate_annotations_as_df(
124131
:param include_comments: enables inclusion of comments info as commentResolved column
125132
:type include_comments: bool
126133
127-
:return: DataFrame on annotations with columns: "imageName", "instanceId" className", "attributeGroupName", "attributeName", "type", "error", "locked", "visible", "trackingId", "probability", "pointLabels", "meta" (geometry information as string), "commentResolved", "classColor"
134+
:return: DataFrame on annotations with columns: "imageName", "instanceId" className", "attributeGroupName", "attributeName", "type", "error", "locked", "visible", "trackingId", "probability", "pointLabels", "meta" (geometry information as string), "commentResolved", "classColor", "groupId"
128135
:rtype: pandas DataFrame
129136
"""
130137

@@ -148,6 +155,7 @@ def aggregate_annotations_as_df(
148155
"pointLabels": [],
149156
"meta": [],
150157
"classColor": [],
158+
"groupId": []
151159
}
152160

153161
if include_comments:
@@ -192,12 +200,9 @@ def __append_annotation(annotation_dict):
192200
annotation_image_name = annotation_path.name.split("___")[0]
193201
annotation_instance_id = 0
194202
for annotation in annotation_json:
195-
196203
annotation_type = annotation.get("type", "mask")
197-
198204
if annotation_type in ['meta', 'tag']:
199205
continue
200-
201206
if annotation_type == "comment":
202207
if include_comments:
203208
comment_resolved = annotation["resolved"]
@@ -215,26 +220,18 @@ def __append_annotation(annotation_dict):
215220
}
216221
)
217222
continue
218-
219223
annotation_instance_id += 1
220-
221224
annotation_class_name = annotation.get("className")
222-
223-
if annotation_class_name:
224-
annotation_class_color = class_name_to_color[annotation_class_name]
225-
else:
225+
if annotation_class_name is None:
226226
raise SABaseException(
227227
0, "Annotation class not found in classes.json"
228228
)
229-
229+
annotation_class_color = class_name_to_color[annotation_class_name]
230+
annotation_group_id = annotation.get("groupId")
230231
annotation_locked = annotation.get("locked")
231-
232232
annotation_visible = annotation.get("visible")
233-
234233
annotation_tracking_id = annotation.get("trackingId")
235-
236234
annotation_meta = None
237-
238235
if annotation_type in ["bbox", "polygon", "polyline", "cuboid"]:
239236
annotation_meta = {"points": annotation["points"]}
240237
elif annotation_type == "point":
@@ -254,13 +251,9 @@ def __append_annotation(annotation_dict):
254251
"connections": annotation["connections"],
255252
"points": annotation["points"]
256253
}
257-
258254
annotation_error = annotation.get('error')
259-
260255
annotation_probability = annotation.get("probability")
261-
262256
annotation_point_labels = annotation.get("pointLabels")
263-
264257
attributes = annotation.get("attributes")
265258

266259
if not attributes:
@@ -277,7 +270,8 @@ def __append_annotation(annotation_dict):
277270
"error": annotation_error,
278271
"probability": annotation_probability,
279272
"pointLabels": annotation_point_labels,
280-
"classColor": annotation_class_color
273+
"classColor": annotation_class_color,
274+
"groupId": annotation_group_id
281275
}
282276
)
283277

@@ -301,7 +295,8 @@ def __append_annotation(annotation_dict):
301295
"error": annotation_error,
302296
"probability": annotation_probability,
303297
"pointLabels": annotation_point_labels,
304-
"classColor": annotation_class_color
298+
"classColor": annotation_class_color,
299+
"groupId": annotation_group_id
305300
}
306301
)
307302

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
import pandas as pd
22

33

4+
def filter_comments(annotations_df, resolved=False):
5+
"""Filter comments on resolve status
6+
7+
:param annotations_df: pandas DataFrame of project annotations
8+
:type annotations_df: pandas.DataFrame
9+
:param resolved: resolved criterion
10+
:type resolved: bool
11+
12+
:return: filtered DataFrame
13+
:rtype: pandas.DataFrame
14+
15+
"""
16+
df = annotations_df[annotations_df["type"] == "comment"]
17+
df = df[df["commentResolved"] == resolved]
18+
19+
return df
20+
21+
422
def filter_annotation_instances(annotations_df, include=None, exclude=None):
523
"""Filter annotation instances from project annotations pandas DataFrame.
624

superannotate/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.1.2"
1+
__version__ = "2.1.3"

tests/sample_project_vector/example_image_1.jpg___objects.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
[
2+
{
3+
"type": "comment",
4+
"x": 621.4069213867188,
5+
"y": 631.5987548828125,
6+
"comments": [
7+
{
8+
"text": "Bordyuri mi mas@ petqa lini parking class-i mej myus mas@ Terrian class-i",
9+
"id": "hovnatan@superannotate.com"
10+
}
11+
],
12+
"resolved": true
13+
},
214
{
315
"type": "bbox",
416
"classId": 55917,

tests/test_annotation_adding.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,7 @@ def test_add_bbox(tmpdir):
8787
export = sa.prepare_export(project, include_fuse=True)
8888
sa.download_export(project, export, tmpdir)
8989

90-
annotations_new_export = json.load(
91-
open(tmpdir / f"{image_name}___objects.json")
92-
)
93-
94-
df = sa.aggregate_annotations_as_df(tmpdir)
90+
df = sa.aggregate_annotations_as_df(tmpdir, include_comments=True)
9591

9692
num = len(df[df["imageName"] == image_name]["instanceId"].unique())
9793

tests/test_basic_images.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,13 @@ def test_basic_images(project_type, name, description, from_folder, tmpdir):
7171
downloaded_classes = json.load(open(tmpdir / "classes.json"))
7272

7373
for a in annotation:
74-
found = False
74+
if "className" not in a:
75+
continue
7576
for c1 in downloaded_classes:
7677
if a["className"] == c1["name"]:
77-
found = True
7878
break
79-
assert found
79+
else:
80+
assert False
8081

8182
input_classes = json.load(open(from_folder / "classes" / "classes.json"))
8283
assert len(downloaded_classes) == len(input_classes)

tests/test_filter_instances.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@
77
PROJECT_DIR = "./tests/sample_project_vector"
88

99

10+
def test_filter_comments(tmpdir):
11+
tmpdir = Path(tmpdir)
12+
13+
# projects = sa.search_projects(PROJECT_NAME_1, return_metadata=True)
14+
# for project in projects:
15+
# sa.delete_project(project)
16+
17+
# project = sa.create_project(PROJECT_NAME_1, "test", "Vector")
18+
19+
not_filtered = sa.aggregate_annotations_as_df(
20+
PROJECT_DIR, include_comments=True
21+
)
22+
23+
filtered_excl = sa.filter_comments(not_filtered, True)
24+
print(str(filtered_excl["meta"].iloc[0]))
25+
26+
1027
def test_filter_instances(tmpdir):
1128
tmpdir = Path(tmpdir)
1229

@@ -89,12 +106,11 @@ def test_df_to_annotations(tmpdir):
89106
# print(df_new["image_name"].value_counts())
90107
# print(df["image_name"].value_counts())
91108
for _index, row in enumerate(df.iterrows()):
92-
found = False
93109
for _, row_2 in enumerate(df_new.iterrows()):
94110
if row_2[1].equals(row[1]):
95-
found = True
96111
break
97-
assert found
112+
else:
113+
assert False
98114
for project in sa.search_projects("test df to annotations 2"):
99115
sa.delete_project(project)
100116
project = sa.create_project("test df to annotations 2", "test", "Vector")
@@ -113,12 +129,20 @@ def test_df_to_annotations_full(tmpdir):
113129
tmpdir = Path(tmpdir)
114130

115131
df = sa.aggregate_annotations_as_df(
116-
PROJECT_DIR, include_classes_wo_annotations=True
132+
PROJECT_DIR, include_classes_wo_annotations=True, include_comments=True
117133
)
118134
sa.df_to_annotations(df, tmpdir)
119135
df_new = sa.aggregate_annotations_as_df(
120-
tmpdir, include_classes_wo_annotations=True
136+
tmpdir, include_classes_wo_annotations=True, include_comments=True
137+
)
138+
for project in sa.search_projects("test df to annotations 4"):
139+
sa.delete_project(project)
140+
project = sa.create_project("test df to annotations 4", "test", "Vector")
141+
sa.upload_images_from_folder_to_project(project, PROJECT_DIR)
142+
sa.create_annotation_classes_from_classes_json(
143+
project, tmpdir / "classes" / "classes.json"
121144
)
145+
sa.upload_annotations_from_folder_to_project(project, tmpdir)
122146
# print(df_new["image_name"].value_counts())
123147
# print(df["image_name"].value_counts())
124148
for _index, row in enumerate(df.iterrows()):

0 commit comments

Comments
 (0)