Skip to content

Commit 3d0962d

Browse files
committed
2 parents b4010b4 + e16a2b2 commit 3d0962d

File tree

118 files changed

+1054
-23
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+1054
-23
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
.PHONY: clean tests stress-tests test_coverage install lint docs dist check_formatting
22

3+
MAKEFLAGS += -j1
4+
35
PYTHON=python3
46
PYLINT=pylint
57
PYTESTS=pytest
68
COVERAGE=coverage
79

810
tests: check_formatting docs
9-
$(PYTESTS) -n auto tests
11+
$(PYTESTS) -n 8 --full-trace tests
1012

1113
stress-tests: SA_STRESS_TESTS=1
1214
stress-tests: tests

superannotate/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ def consensus(*args, **kwargs):
8989
coco_split_dataset, convert_project_type, export_annotation,
9090
import_annotation, convert_json_version
9191
)
92+
from .ml.ml_funcs import (
93+
delete_model, download_model, plot_model_metrics, run_prediction,
94+
run_segmentation, run_training, stop_model_training
95+
)
96+
from .ml.ml_models import search_models
9297

9398
from .old_to_new_format_convertor import update_json_format
9499
from .version import __version__

superannotate/api.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ def send_request(self, req_type, path, params=None, json_req=None):
123123
)
124124
url = self._main_endpoint + path
125125

126+
if params is not None:
127+
for key, value in params.items():
128+
if isinstance(value, str):
129+
params[key] = value.replace("\\", "\\\\")
130+
126131
req = requests.Request(
127132
method=req_type, url=url, json=json_req, params=params
128133
)

superannotate/common.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
import time
77
from tqdm import tqdm
88
from pathlib import Path
9-
9+
from enum import IntEnum
1010
import numpy as np
1111
from PIL import Image
1212

1313
logger = logging.getLogger("superannotate-python-sdk")
1414

1515
_PROJECT_TYPES = {"Vector": 1, "Pixel": 2}
16+
1617
_ANNOTATION_STATUSES = {
1718
"NotStarted": 1,
1819
"InProgress": 2,
@@ -22,6 +23,44 @@
2223
"Skipped": 6
2324
}
2425
_USER_ROLES = {"Admin": 2, "Annotator": 3, "QA": 4, "Customer": 5, "Viewer": 6}
26+
_AVAILABLE_SEGMENTATION_MODELS = ['autonomous', 'generic']
27+
_MODEL_TRAINING_STATUSES = {
28+
"NotStarted": 1,
29+
"InProgress": 2,
30+
"Completed": 3,
31+
"FailedBeforeEvaluation": 4,
32+
"FailedAfterEvaluation": 5,
33+
"FailedAfterEvaluationWithSavedModel": 6
34+
}
35+
36+
_PREDICTION_SEGMENTATION_STATUSES = {
37+
"NotStarted": 1,
38+
"InProgress": 2,
39+
"Completed": 3,
40+
"Failed": 4
41+
}
42+
43+
44+
def prediction_segmentation_status_from_str_to_int(status):
45+
return _PREDICTION_SEGMENTATION_STATUSES[status]
46+
47+
48+
def prediction_segmentation_status_from_int_to_str(status):
49+
for idx, item in _PREDICTION_SEGMENTATION_STATUSES.items():
50+
if item == status:
51+
return idx
52+
raise RuntimeError("NA segmentation/prediction status")
53+
54+
55+
def model_training_status_int_to_str(project_status):
56+
for item in _MODEL_TRAINING_STATUSES:
57+
if _MODEL_TRAINING_STATUSES[item] == project_status:
58+
return item
59+
raise RuntimeError("NA training status")
60+
61+
62+
def model_training_status_str_to_int(project_status):
63+
return _MODEL_TRAINING_STATUSES[project_status]
2564

2665

2766
def image_path_to_annotation_paths(image_path, project_type):
@@ -257,6 +296,14 @@ def write_to_json(output_path, json_data):
257296
} # Resolution limit
258297

259298

299+
def process_api_response(data):
300+
logger.info("Maintenance operations are being performed")
301+
if 'metadata' not in data:
302+
return data
303+
304+
return data['data']
305+
306+
260307
def tqdm_converter(
261308
total_num, images_converted, images_not_converted, finish_event
262309
):

superannotate/db/images.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
search_annotation_classes
2525
)
2626
from .project_api import get_project_metadata_bare
27+
from ..common import process_api_response
28+
from ..parameter_decorators import project_metadata
2729

2830
logger = logging.getLogger("superannotate-python-sdk")
2931

@@ -43,7 +45,10 @@ def _get_project_root_folder_id(project):
4345
)
4446
if not response.ok:
4547
raise SABaseException(response.status_code, response.text)
46-
return response.json()['folder_id']
48+
49+
response = process_api_response(response.json())
50+
51+
return response['folder_id']
4752

4853

4954
def search_images(
@@ -94,14 +99,16 @@ def search_images(
9499
)
95100
if response.ok:
96101
# print(response.json())
97-
results = response.json()["data"]
102+
response = process_api_response(response.json())
103+
results = response["data"]
98104
total_got += len(results)
99105
for r in results:
100106
if return_metadata:
101107
result_list.append(r)
102108
else:
103109
result_list.append(r["name"])
104-
if response.json()["count"] <= total_got:
110+
111+
if response["count"] <= total_got:
105112
break
106113
params["offset"] = total_got
107114
# print(
@@ -126,7 +133,8 @@ def process_result(x):
126133
return result_list
127134

128135

129-
def get_image_metadata(project, image_name):
136+
@project_metadata
137+
def get_image_metadata(project, image_names):
130138
"""Returns image metadata
131139
132140
:param project: project name or metadata of the project
@@ -137,11 +145,42 @@ def get_image_metadata(project, image_name):
137145
:return: metadata of image
138146
:rtype: dict
139147
"""
140-
images = search_images(project, image_name, return_metadata=True)
141-
for image in images:
142-
if image["name"] == image_name:
143-
return image
144-
raise SABaseException(0, "Image " + image_name + " doesn't exist.")
148+
if isinstance(image_names, str):
149+
image_names = [image_names]
150+
151+
json_req = {
152+
'project_id': project['id'],
153+
'team_id': _api.team_id,
154+
'names': image_names
155+
}
156+
response = _api.send_request(
157+
req_type='POST',
158+
path='/images/getBulk',
159+
json_req=json_req,
160+
)
161+
162+
metadata = response.json()
163+
if len(metadata) == 0:
164+
raise SABaseException(
165+
0,
166+
f"None of the images in {image_names} exist in the provided project"
167+
)
168+
for item in metadata:
169+
item['annotation_status'] = common.annotation_status_int_to_str(
170+
item['annotation_status']
171+
)
172+
item['prediction_status'
173+
] = common.prediction_segmentation_status_from_int_to_str(
174+
item['prediction_status']
175+
)
176+
item['segmentation_status'
177+
] = common.prediction_segmentation_status_from_int_to_str(
178+
item['segmentation_status']
179+
)
180+
181+
if len(metadata) == 1:
182+
return metadata[0]
183+
return metadata
145184

146185

147186
def set_image_annotation_status(project, image_name, annotation_status):
@@ -174,7 +213,10 @@ def set_image_annotation_status(project, image_name, annotation_status):
174213
)
175214
if not response.ok:
176215
raise SABaseException(response.status_code, response.text)
177-
return response.json()
216+
217+
response = process_api_response(response.json())
218+
219+
return response
178220

179221

180222
def add_annotation_comment_to_image(

superannotate/db/project_api.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
_api = API.get_instance()
1313

1414

15-
def get_project_metadata_bare(project_name):
15+
def get_project_metadata_bare(project_name, include_complete_image_count=False):
1616
"""Returns project metadata
1717
1818
:param project_name: project name
@@ -21,7 +21,11 @@ def get_project_metadata_bare(project_name):
2121
:return: metadata of project
2222
:rtype: dict
2323
"""
24-
projects = search_projects(project_name, return_metadata=True)
24+
projects = search_projects(
25+
project_name,
26+
return_metadata=True,
27+
include_complete_image_count=include_complete_image_count
28+
)
2529
results = []
2630
for project in projects:
2731
if project["name"] == project_name:

superannotate/db/project_images.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def copy_image(
181181
else:
182182
new_name = new_name[:m.start() +
183183
2] + str(num + 1) + ")" + extension
184+
184185
upload_image_to_project(destination_project, img_b, new_name)
185186
if include_annotations:
186187
annotations = get_image_annotations(source_project, image_name)

superannotate/db/project_metadata.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ def get_project_metadata(
1515
include_annotation_classes=False,
1616
include_settings=False,
1717
include_workflow=False,
18-
include_contributors=False
18+
include_contributors=False,
19+
include_complete_image_count=False
1920
):
2021
"""Returns project metadata
2122
@@ -38,7 +39,9 @@ def get_project_metadata(
3839
:rtype: dict
3940
"""
4041
if not isinstance(project, dict):
41-
project = get_project_metadata_bare(project)
42+
project = get_project_metadata_bare(
43+
project, include_complete_image_count
44+
)
4245
result = copy.deepcopy(project)
4346
if include_annotation_classes:
4447
result["annotation_classes"] = search_annotation_classes(project)

superannotate/db/search_projects.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
from ..api import API
44
from ..exceptions import (SABaseException)
5-
5+
from ..common import process_api_response
66
logger = logging.getLogger("superannotate-python-sdk")
77
_api = API.get_instance()
88

99

10-
def search_projects(name=None, return_metadata=False):
10+
def search_projects(
11+
name=None, return_metadata=False, include_complete_image_count=False
12+
):
1113
"""Project name based case-insensitive search for projects.
1214
If **name** is None, all the projects will be returned.
1315
@@ -20,17 +22,21 @@ def search_projects(name=None, return_metadata=False):
2022
:rtype: list of strs or dicts
2123
"""
2224
result_list = []
23-
params = {'team_id': str(_api.team_id), 'offset': 0}
25+
params = {
26+
'team_id': str(_api.team_id),
27+
'offset': 0,
28+
'completeImagesCount': include_complete_image_count
29+
}
2430
if name is not None:
2531
params['name'] = name
2632
while True:
2733
response = _api.send_request(
2834
req_type='GET', path='/projects', params=params
2935
)
3036
if response.ok:
31-
new_results = response.json()
37+
new_results = process_api_response(response.json())
3238
result_list += new_results["data"]
33-
if response.json()["count"] <= len(result_list):
39+
if new_results["count"] <= len(result_list):
3440
break
3541
params["offset"] = len(result_list)
3642
else:

superannotate/ml/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)