annolab 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- annolab/__init__.py +6 -0
- annolab/annolab.py +76 -0
- annolab/annotation.py +37 -0
- annolab/annotation_relation.py +28 -0
- annolab/api_helper.py +97 -0
- annolab/endpoints.py +114 -0
- annolab/project.py +359 -0
- annolab/project_export.py +100 -0
- annolab/project_import.py +253 -0
- annolab/util/__init__.py +0 -0
- annolab/util/cached_property.py +14 -0
- annolab-0.3.2.dist-info/METADATA +175 -0
- annolab-0.3.2.dist-info/RECORD +16 -0
- annolab-0.3.2.dist-info/WHEEL +5 -0
- annolab-0.3.2.dist-info/licenses/LICENSE +202 -0
- annolab-0.3.2.dist-info/top_level.txt +1 -0
annolab/__init__.py
ADDED
annolab/annolab.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from annolab.project_import import ProjectImport
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from annolab import endpoints
|
|
5
|
+
from annolab.project import Project
|
|
6
|
+
from annolab.api_helper import ApiHelper
|
|
7
|
+
|
|
8
|
+
class AnnoLab:
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
api_key = None,
|
|
13
|
+
api_url = 'https://api.annolab.ai',
|
|
14
|
+
):
|
|
15
|
+
self.__api = ApiHelper(api_key=api_key, api_url=api_url)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def api_key_info(self):
|
|
19
|
+
return self.__api.api_key_info
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def default_owner(self):
|
|
24
|
+
"""
|
|
25
|
+
Returns the default group to use for the api key.
|
|
26
|
+
The default group is the group representing the single user.
|
|
27
|
+
"""
|
|
28
|
+
return self.__api.default_owner
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def find_project(self, name: str, owner_name: str = None):
|
|
32
|
+
"""
|
|
33
|
+
Find a project by name and (optionally) group name.
|
|
34
|
+
If group name is not passed, the user's default group is used.
|
|
35
|
+
"""
|
|
36
|
+
owner_name = owner_name or self.default_owner['groupName']
|
|
37
|
+
|
|
38
|
+
res = self.__api.get_request(
|
|
39
|
+
endpoints.Project.get_group_project(owner_name, name)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return Project.create_from_response_json(res.json(), self.__api)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_project(self, name: str, owner_name: str = None, is_public = False):
|
|
46
|
+
"""
|
|
47
|
+
Create a project.
|
|
48
|
+
If group name is not passed, the user's default group is used.
|
|
49
|
+
"""
|
|
50
|
+
owner_name = owner_name or self.default_owner['groupName']
|
|
51
|
+
|
|
52
|
+
res = self.__api.post_request(
|
|
53
|
+
endpoints.Project.post_create(),
|
|
54
|
+
{
|
|
55
|
+
'name': name,
|
|
56
|
+
'groupName': owner_name,
|
|
57
|
+
'isPublic': is_public
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return Project.create_from_response_json(res.json(), self.__api)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def create_project_from_export(self, filepath: str, name: str = None, owner_name: str = None, is_public=False):
|
|
65
|
+
if (name is None):
|
|
66
|
+
name = os.path.basename(filepath).split('.')[0]
|
|
67
|
+
|
|
68
|
+
project = self.create_project(name, owner_name, is_public=is_public)
|
|
69
|
+
project_import = ProjectImport(filepath, project, owner_name)
|
|
70
|
+
|
|
71
|
+
project_import.unzip_export()
|
|
72
|
+
project_import.import_all()
|
|
73
|
+
project_import.cleanup()
|
|
74
|
+
|
|
75
|
+
return project
|
|
76
|
+
|
annolab/annotation.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
class Annotation:
|
|
4
|
+
|
|
5
|
+
@staticmethod
|
|
6
|
+
def create_api_annotation(dict: Dict):
|
|
7
|
+
"""
|
|
8
|
+
Maps an sdk annotation dict to an api annotation dict, for use in calls to the api.
|
|
9
|
+
|
|
10
|
+
Annotation parameters:
|
|
11
|
+
type: str (Required)
|
|
12
|
+
client_id: str (Optional, Required if passing relations)
|
|
13
|
+
value: str (Optional)
|
|
14
|
+
offsets: [int, int] (Optional)
|
|
15
|
+
text_bounds { 'type': 'Polygon', coordinates: List[List[List[int]]] } (Optional)
|
|
16
|
+
bbox: [int, int, int, int] (Optional, Ignored if text_bounds are passed)
|
|
17
|
+
layer: str (Optional)
|
|
18
|
+
page: int (Optional)
|
|
19
|
+
reviewed bool (Optional)
|
|
20
|
+
"""
|
|
21
|
+
annotation = { 'annoTypeIdentifier': dict['type'] }
|
|
22
|
+
|
|
23
|
+
if ('client_id' in dict): annotation['clientId'] = str(dict['client_id'])
|
|
24
|
+
if ('offsets' in dict): annotation['offsets'] = dict['offsets']
|
|
25
|
+
if ('value' in dict): annotation['value'] = dict['value']
|
|
26
|
+
if ('bbox' in dict): annotation['bbox'] = dict['bbox']
|
|
27
|
+
if ('text_bounds' in dict): annotation['textBounds'] = dict['text_bounds']
|
|
28
|
+
if ('image_bounds' in dict): annotation['imageBounds'] = dict['image_bounds']
|
|
29
|
+
if ('layer' in dict): annotation['layerIdentifier'] = dict['layer']
|
|
30
|
+
if ('page' in dict): annotation['pageNumber'] = dict['page']
|
|
31
|
+
if ('endPage' in dict): annotation['endPageNumber'] = dict['endPage']
|
|
32
|
+
if ('reviewed' in dict): annotation['isReviewed'] = dict['reviewed']
|
|
33
|
+
if ('source' in dict): annotation['sourceIdentifier'] = dict['source']
|
|
34
|
+
if ('directory' in dict): annotation['directoryIdentifier'] = dict['directory']
|
|
35
|
+
if ('project' in dict): annotation['projectIdentifier'] = dict['project']
|
|
36
|
+
|
|
37
|
+
return annotation
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AnnotationRelation:
|
|
5
|
+
|
|
6
|
+
@staticmethod
|
|
7
|
+
def create_api_relation(dict: Dict):
|
|
8
|
+
"""
|
|
9
|
+
Maps an sdk relation dict to an api annotation relation dict, for use in calls to the api.
|
|
10
|
+
|
|
11
|
+
AnnotationRelation parameters:
|
|
12
|
+
annotations: [Union[str, int], Union[str, int]]
|
|
13
|
+
type: str
|
|
14
|
+
value: str
|
|
15
|
+
reviewed bool (Optional)
|
|
16
|
+
project Union[str, int]
|
|
17
|
+
"""
|
|
18
|
+
relation = {
|
|
19
|
+
'predecessorId': str(dict['annotations'][0]),
|
|
20
|
+
'successorId': str(dict['annotations'][1])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if ('type' in dict): relation['annoTypeIdentifier'] = dict['type']
|
|
24
|
+
if ('value' in dict): relation['value'] = dict['value']
|
|
25
|
+
if ('reviewed' in dict): relation['isReviewed'] = dict['reviewed']
|
|
26
|
+
if ('project' in dict): relation['projectIdentifier'] = dict['project']
|
|
27
|
+
|
|
28
|
+
return relation
|
annolab/api_helper.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
import requests
|
|
3
|
+
from urllib import parse
|
|
4
|
+
import logging
|
|
5
|
+
from requests.models import Response
|
|
6
|
+
|
|
7
|
+
import annolab
|
|
8
|
+
from annolab import endpoints
|
|
9
|
+
from annolab.util.cached_property import cached_property
|
|
10
|
+
|
|
11
|
+
class ApiHelper(object):
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
api_key = None,
|
|
16
|
+
api_url = 'https://api.annolab.ai',
|
|
17
|
+
):
|
|
18
|
+
self.api_url = api_url
|
|
19
|
+
self.api_key = api_key or annolab.api_key
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def __auth_header(self, key: str = None):
|
|
24
|
+
key = key or self.api_key
|
|
25
|
+
return { 'Authorization': f'Api-Key {key}' }
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@cached_property
|
|
29
|
+
def api_key_info(self):
|
|
30
|
+
return self.get_request(endpoints.ApiKey.get_api_key_info()).json()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
'''
|
|
34
|
+
Returns the default group to use for the api key.
|
|
35
|
+
The default group is the group representing the single user.
|
|
36
|
+
'''
|
|
37
|
+
@cached_property
|
|
38
|
+
def default_owner(self):
|
|
39
|
+
default_owner = None
|
|
40
|
+
for group in self.api_key_info['groups']:
|
|
41
|
+
if (group['isSingleUser'] is True):
|
|
42
|
+
default_owner = group
|
|
43
|
+
|
|
44
|
+
return default_owner
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_request(self, path: str, body: Dict[str, Any] = None, params: dict = None) -> Response:
|
|
48
|
+
resp = requests.get(
|
|
49
|
+
parse.urljoin(self.api_url, path),
|
|
50
|
+
headers=self.__auth_header,
|
|
51
|
+
json=body
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.__handle_non_2xx_response(resp)
|
|
55
|
+
|
|
56
|
+
return resp
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def post_request(self, path: str, body: Dict[str, Any] = None, params: dict = None, timeout: float = 30.0):
|
|
60
|
+
resp = requests.post(
|
|
61
|
+
parse.urljoin(self.api_url, path),
|
|
62
|
+
headers=self.__auth_header,
|
|
63
|
+
json=body,
|
|
64
|
+
params=params,
|
|
65
|
+
timeout=timeout
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
self.__handle_non_2xx_response(resp)
|
|
69
|
+
|
|
70
|
+
return resp
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def put_request(self, path: str, data: Any = None, headers = None, params: dict = None, timeout: float = 30.0):
|
|
74
|
+
resp = requests.put(
|
|
75
|
+
parse.urljoin(self.api_url, path),
|
|
76
|
+
headers=headers,
|
|
77
|
+
data=data,
|
|
78
|
+
params=params,
|
|
79
|
+
timeout=timeout
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.__handle_non_2xx_response(resp)
|
|
83
|
+
|
|
84
|
+
return resp
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def __handle_non_2xx_response(self, resp: Response):
|
|
88
|
+
if (resp.status_code >= 300):
|
|
89
|
+
try:
|
|
90
|
+
resp_body = resp.json()
|
|
91
|
+
except:
|
|
92
|
+
resp_body = {}
|
|
93
|
+
|
|
94
|
+
message = resp_body['message'] if 'message' in resp_body else 'Unknown Error'
|
|
95
|
+
|
|
96
|
+
logging.error(f'{resp.request.method} {resp.request.path_url} failed with message: {message}')
|
|
97
|
+
resp.raise_for_status()
|
annolab/endpoints.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
class Project:
|
|
2
|
+
|
|
3
|
+
@staticmethod
|
|
4
|
+
def post_create():
|
|
5
|
+
return 'v1/project/create'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
def get_group_project(groupName: str, projectName: str):
|
|
10
|
+
return f'v1/project/{groupName}/{projectName}'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def get_using_id(id: int):
|
|
15
|
+
return f'v1/project/{id}'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Export:
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def post_export_project():
|
|
22
|
+
return 'v1/export/project'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Directory:
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def post_create():
|
|
29
|
+
return 'v1/directory/create'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Annotation:
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def post_create():
|
|
36
|
+
return 'v1/annotation/create'
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def post_bulk_create():
|
|
40
|
+
return 'v1/annotation/bulk-create'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AnnotationType:
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def post_create():
|
|
47
|
+
return 'v1/annotation-type/create'
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AnnotationRelation:
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def post_create():
|
|
54
|
+
return 'v1/relation/create'
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def post_bulk_create():
|
|
59
|
+
return 'v1/relation/bulk-create'
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AnnotationLayer:
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def post_create():
|
|
66
|
+
return 'v1/layer/create'
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Source:
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def get_source_by_id(source_ref_id: int):
|
|
73
|
+
return f'v1/source/{source_ref_id}'
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def get_source_by_path(owner_name: str, project_name: str, directory_name: str, source_ref_name: str):
|
|
78
|
+
return f'v1/source/{owner_name}/{project_name}/{directory_name}/{source_ref_name}'
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def post_create_text():
|
|
83
|
+
return 'v1/source/create-text'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def post_initialize_pdf():
|
|
88
|
+
return 'v1/source/init-pdf'
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def post_create_pdf():
|
|
93
|
+
return 'v1/source/create-pdf'
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def post_annotations(owner_name: str, project_name: str, directory_name: str, source_ref_name: str):
|
|
98
|
+
return f'v1/source/{owner_name}/{project_name}/{directory_name}/{source_ref_name}/annotations'
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def delete_using_name(owner_name: str, project_name: str, directory_name: str, source_ref_name: str):
|
|
103
|
+
return f'v1/source/{owner_name}/{project_name}/{directory_name}/{source_ref_name}'
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def delete_using_id(id: int):
|
|
108
|
+
return f'v1/source/{id}'
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ApiKey:
|
|
112
|
+
@staticmethod
|
|
113
|
+
def get_api_key_info():
|
|
114
|
+
return 'v1/api-key/info'
|