Skip to content

Commit ff70b00

Browse files
committed
Fix annotation classes and add create-project
1 parent cb215bf commit ff70b00

File tree

10 files changed

+257
-63
lines changed

10 files changed

+257
-63
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,6 @@ sample_tif_files
133133

134134
# Vscode settings folder
135135
.vscode/
136+
137+
# python virtual env for SDK testing
138+
venv_sa_conv

docs/source/cli.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ To initialize CLI (and SDK) with team token:
3636
3737
----------
3838

39+
.. _ref_create_project:
40+
41+
Creating a project
42+
~~~~~~~~~~~~~~~~~~
43+
44+
To create a new project:
45+
46+
.. code-block:: bash
47+
48+
superannotate create-project --name <project_name> --description <project_description> --type <project_type Vector or Pixel>
49+
50+
----------
51+
3952
.. _ref_upload_images:
4053

4154
Uploading images

superannotate/__main__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def main():
4444
command = sys.argv[1]
4545
further_args = sys.argv[2:]
4646

47-
if command == "upload-images":
47+
if command == "create-project":
48+
create_project(further_args)
49+
elif command == "upload-images":
4850
image_upload(further_args)
4951
elif command == "upload-videos":
5052
video_upload(further_args)
@@ -122,7 +124,8 @@ def preannotations_upload(args, annotations=False):
122124
)
123125
args.folder = tempdir_path
124126
sa.create_annotation_classes_from_classes_json(
125-
args.project, Path(args.folder) / "classes" / "classes.json"
127+
args.project,
128+
Path(args.folder) / "classes" / "classes.json"
126129
)
127130

128131
if annotations:
@@ -135,6 +138,20 @@ def preannotations_upload(args, annotations=False):
135138
)
136139

137140

141+
def create_project(args):
142+
parser = argparse.ArgumentParser()
143+
parser.add_argument('--name', required=True, help='Project name to create')
144+
parser.add_argument(
145+
'--description', required=True, help='Project description'
146+
)
147+
parser.add_argument(
148+
'--type', required=True, help='Project type Vector or Pixel'
149+
)
150+
args = parser.parse_args(args)
151+
152+
sa.create_project(args.name, args.description, args.type)
153+
154+
138155
def video_upload(args):
139156
parser = argparse.ArgumentParser()
140157
parser.add_argument(

superannotate/analytics/common.py

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,17 @@ def aggregate_annotations_as_df(
183183
)
184184
classes_json = json.load(open(classes_path))
185185
class_name_to_color = {}
186+
class_group_name_to_values = {}
186187
for annotation_class in classes_json:
187188
name = annotation_class["name"]
188189
color = annotation_class["color"]
189190
class_name_to_color[name] = color
191+
class_group_name_to_values[name] = {}
192+
for attribute_group in annotation_class["attribute_groups"]:
193+
class_group_name_to_values[name][attribute_group["name"]] = []
194+
for attribute in attribute_group["attributes"]:
195+
class_group_name_to_values[name][attribute_group["name"]
196+
].append(attribute["name"])
190197

191198
def __append_annotation(annotation_dict):
192199
for annotation_key in annotation_data:
@@ -258,10 +265,12 @@ def __get_image_metadata(image_name, annotations):
258265
continue
259266
annotation_instance_id += 1
260267
annotation_class_name = annotation.get("className")
261-
if annotation_class_name is None:
262-
raise SABaseException(
263-
0, "Annotation class not found in classes.json"
268+
if annotation_class_name is None or annotation_class_name not in class_name_to_color:
269+
logger.warning(
270+
"Annotation class %s not found in classes json. Skipping.",
271+
annotation_class_name
264272
)
273+
continue
265274
annotation_class_color = class_name_to_color[annotation_class_name]
266275
annotation_group_id = annotation.get("groupId")
267276
annotation_locked = annotation.get("locked")
@@ -332,37 +341,50 @@ def __get_image_metadata(image_name, annotations):
332341
}
333342
annotation_dict.update(image_metadata)
334343
__append_annotation(annotation_dict)
335-
336-
for attribute in attributes:
337-
338-
attribute_group = attribute.get("groupName")
339-
attribute_name = attribute.get('name')
340-
annotation_dict = {
341-
"imageName": image_name,
342-
"instanceId": annotation_instance_id,
343-
"className": annotation_class_name,
344-
"attributeGroupName": attribute_group,
345-
"attributeName": attribute_name,
346-
"type": annotation_type,
347-
"locked": annotation_locked,
348-
"visible": annotation_visible,
349-
"trackingId": annotation_tracking_id,
350-
"meta": annotation_meta,
351-
"error": annotation_error,
352-
"probability": annotation_probability,
353-
"pointLabels": annotation_point_labels,
354-
"classColor": annotation_class_color,
355-
"groupId": annotation_group_id,
356-
"createdAt": annotation_created_at,
357-
"creatorRole": annotation_creator_role,
358-
"creatorEmail": annotation_creator_email,
359-
"creationType": annotation_creation_type,
360-
"updatedAt": annotation_updated_at,
361-
"updatorRole": annotation_updator_role,
362-
"updatorEmail": annotation_updator_email
363-
}
364-
annotation_dict.update(image_metadata)
365-
__append_annotation(annotation_dict)
344+
else:
345+
for attribute in attributes:
346+
attribute_group = attribute.get("groupName")
347+
attribute_name = attribute.get('name')
348+
if attribute_group not in class_group_name_to_values[
349+
annotation_class_name]:
350+
logger.warning(
351+
"Annotation class group %s not in classes json. Skipping.",
352+
attribute_group
353+
)
354+
continue
355+
if attribute_name not in class_group_name_to_values[
356+
annotation_class_name][attribute_group]:
357+
logger.warning(
358+
"Annotation class group value %s not in classes json. Skipping.",
359+
attribute_name
360+
)
361+
continue
362+
annotation_dict = {
363+
"imageName": image_name,
364+
"instanceId": annotation_instance_id,
365+
"className": annotation_class_name,
366+
"attributeGroupName": attribute_group,
367+
"attributeName": attribute_name,
368+
"type": annotation_type,
369+
"locked": annotation_locked,
370+
"visible": annotation_visible,
371+
"trackingId": annotation_tracking_id,
372+
"meta": annotation_meta,
373+
"error": annotation_error,
374+
"probability": annotation_probability,
375+
"pointLabels": annotation_point_labels,
376+
"classColor": annotation_class_color,
377+
"groupId": annotation_group_id,
378+
"createdAt": annotation_created_at,
379+
"creatorRole": annotation_creator_role,
380+
"creatorEmail": annotation_creator_email,
381+
"creationType": annotation_creation_type,
382+
"updatedAt": annotation_updated_at,
383+
"updatorRole": annotation_updator_role,
384+
"updatorEmail": annotation_updator_email
385+
}
386+
annotation_dict.update(image_metadata)
387+
__append_annotation(annotation_dict)
366388

367389
df = pd.DataFrame(annotation_data)
368390

superannotate/db/annotation_classes.py

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,40 @@ def create_annotation_classes_from_classes_json(
160160
else:
161161
new_classes.append(cs)
162162

163-
params = {
164-
'team_id': team_id,
165-
'project_id': project_id,
166-
}
167-
data = {"classes": new_classes}
168-
response = _api.send_request(
169-
req_type='POST', path='/classes', params=params, json_req=data
170-
)
171-
if not response.ok:
172-
raise SABaseException(
173-
response.status_code, "Couldn't create classes " + response.text
163+
res = []
164+
165+
def del_unn(d):
166+
for s in [
167+
"updatedAt", "createdAt", "id", "project_id", "group_id",
168+
"class_id", "count"
169+
]:
170+
if s in d:
171+
del d[s]
172+
173+
for annotation_class in new_classes:
174+
del_unn(annotation_class)
175+
for attribute_group in annotation_class["attribute_groups"]:
176+
del_unn(attribute_group)
177+
for attribute in attribute_group["attributes"]:
178+
del_unn(attribute)
179+
180+
CHUNK_SIZE = 2000
181+
for i in range(0, len(new_classes), CHUNK_SIZE):
182+
params = {
183+
'team_id': team_id,
184+
'project_id': project_id,
185+
}
186+
data = {"classes": new_classes[i:i + CHUNK_SIZE]}
187+
response = _api.send_request(
188+
req_type='POST', path='/classes', params=params, json_req=data
174189
)
175-
res = response.json()
190+
if not response.ok:
191+
raise SABaseException(
192+
response.status_code, "Couldn't create classes " + response.text
193+
)
194+
res += response.json()
195+
196+
assert len(res) == len(new_classes)
176197
return res
177198

178199

@@ -280,12 +301,13 @@ def fill_class_and_attribute_names(annotations_json, annotation_classes_dict):
280301
r["className"] = annotation_classes_dict[r["classId"]]["name"]
281302
if "attributes" in r:
282303
for attribute in r["attributes"]:
283-
attribute["groupName"] = annotation_classes_dict[
284-
r["classId"]]["attribute_groups"][attribute["groupId"]
285-
]["name"]
286-
attribute["name"] = annotation_classes_dict[
287-
r["classId"]]["attribute_groups"][
288-
attribute["groupId"]]["attributes"][attribute["id"]]
304+
if "groupId" in attribute and "id" in "attribute":
305+
attribute["groupName"] = annotation_classes_dict[
306+
r["classId"]]["attribute_groups"][
307+
attribute["groupId"]]["name"]
308+
attribute["name"] = annotation_classes_dict[
309+
r["classId"]]["attribute_groups"][attribute[
310+
"groupId"]]["attributes"][attribute["id"]]
289311

290312

291313
def fill_class_and_attribute_ids(annotation_json, annotation_classes_dict):
@@ -303,12 +325,28 @@ def fill_class_and_attribute_ids(annotation_json, annotation_classes_dict):
303325
class_id = annotation_classes_dict[annotation_class_name]["id"]
304326
ann["classId"] = class_id
305327
for attribute in ann["attributes"]:
306-
attribute["groupId"] = annotation_classes_dict[
328+
if attribute["groupName"] in annotation_classes_dict[
329+
annotation_class_name]["attribute_groups"]:
330+
attribute["groupId"] = annotation_classes_dict[
331+
annotation_class_name]["attribute_groups"][
332+
attribute["groupName"]]["id"]
333+
else:
334+
logger.warning(
335+
"Couldn't find annotation group %s", attribute["groupName"]
336+
)
337+
continue
338+
if attribute["name"] in annotation_classes_dict[
307339
annotation_class_name]["attribute_groups"][
308-
attribute["groupName"]]["id"]
309-
attribute["id"] = annotation_classes_dict[annotation_class_name][
310-
"attribute_groups"][attribute["groupName"]]["attributes"][
311-
attribute["name"]]
340+
attribute["groupName"]]["attributes"]:
341+
attribute["id"] = annotation_classes_dict[
342+
annotation_class_name]["attribute_groups"][
343+
attribute["groupName"]]["attributes"][attribute["name"]]
344+
else:
345+
logger.warning(
346+
"Couldn't find annotation name %s in annotation group %s",
347+
attribute["name"], attribute["groupName"]
348+
)
349+
del attribute["groupId"]
312350

313351

314352
def get_annotation_classes_id_to_name(annotation_classes):

superannotate/version.py

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

tests/sample_project_vector/example_image_1.jpg___objects.json

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,96 @@
6464
"updatedBy": null,
6565
"className": "Personal vehicle"
6666
},
67+
{
68+
"type": "bbox",
69+
"classId": 72274,
70+
"probability": 100,
71+
"points": {
72+
"x1": 480.0,
73+
"x2": 490.0,
74+
"y1": 340.0,
75+
"y2": 350.0
76+
},
77+
"groupId": 0,
78+
"pointLabels": {},
79+
"locked": false,
80+
"visible": false,
81+
"attributes": [
82+
{
83+
"id": 117845,
84+
"groupId": 28230,
85+
"name": "2",
86+
"groupName": "Num doors"
87+
}
88+
],
89+
"error": null,
90+
"createdAt": null,
91+
"createdBy": null,
92+
"creationType": null,
93+
"updatedAt": null,
94+
"updatedBy": null,
95+
"className": "Personal vehicle1"
96+
},
97+
{
98+
"type": "bbox",
99+
"classId": 72274,
100+
"probability": 100,
101+
"points": {
102+
"x1": 500.0,
103+
"x2": 510.0,
104+
"y1": 340.0,
105+
"y2": 350.0
106+
},
107+
"groupId": 0,
108+
"pointLabels": {},
109+
"locked": false,
110+
"visible": false,
111+
"attributes": [
112+
{
113+
"id": 117845,
114+
"groupId": 28230,
115+
"name": "10",
116+
"groupName": "Num doors"
117+
}
118+
],
119+
"error": null,
120+
"createdAt": null,
121+
"createdBy": null,
122+
"creationType": null,
123+
"updatedAt": null,
124+
"updatedBy": null,
125+
"className": "Personal vehicle"
126+
},
127+
{
128+
"type": "bbox",
129+
"classId": 72274,
130+
"probability": 100,
131+
"points": {
132+
"x1": 520.0,
133+
"x2": 530.0,
134+
"y1": 340.0,
135+
"y2": 350.0
136+
},
137+
"groupId": 0,
138+
"pointLabels": {},
139+
"locked": false,
140+
"visible": false,
141+
"attributes": [
142+
{
143+
"id": 117845,
144+
"groupId": 28230,
145+
"name": "4",
146+
"groupName": "Num doors1"
147+
}
148+
],
149+
"error": null,
150+
"createdAt": null,
151+
"createdBy": null,
152+
"creationType": null,
153+
"updatedAt": null,
154+
"updatedBy": null,
155+
"className": "Personal vehicle"
156+
},
67157
{
68158
"type": "template",
69159
"classId": 72274,

0 commit comments

Comments
 (0)