pollen-smart-api 3.145.1__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.
- pollen_smart_api-3.145.1.dist-info/METADATA +21 -0
- pollen_smart_api-3.145.1.dist-info/RECORD +39 -0
- pollen_smart_api-3.145.1.dist-info/WHEEL +5 -0
- pollen_smart_api-3.145.1.dist-info/top_level.txt +1 -0
- smapi/AbstractRestApi.py +260 -0
- smapi/AnalysisResultsRestApi.py +322 -0
- smapi/ChartRestApi.py +558 -0
- smapi/ClassificationRestApi.py +307 -0
- smapi/DataReference.py +131 -0
- smapi/DataTablesRestApi.py +206 -0
- smapi/EntitiesRestApi.py +526 -0
- smapi/Error.py +88 -0
- smapi/FigureError.py +1053 -0
- smapi/FigureRestApi.py +2176 -0
- smapi/FileLoadersRestApi.py +194 -0
- smapi/FilesRestApi.py +483 -0
- smapi/FolderRestApi.py +338 -0
- smapi/ImagesRestApi.py +513 -0
- smapi/LicenseRestApi.py +168 -0
- smapi/MeasurementResultsRestApi.py +291 -0
- smapi/MeasurementsRestApi.py +206 -0
- smapi/NegativeExamples.py +104 -0
- smapi/PipelineExecutionPreviewsRestApi.py +70 -0
- smapi/PipelineExecutionRestApi.py +1872 -0
- smapi/PipelineExecutionforErrorTest.py +665 -0
- smapi/PipelineRestApi.py +347 -0
- smapi/PlatformRestApi.py +46 -0
- smapi/PluginRestApi.py +273 -0
- smapi/RestApi.py +121 -0
- smapi/RestSession.py +344 -0
- smapi/RestWebsocket.py +239 -0
- smapi/SettingsRestApi.py +134 -0
- smapi/SignalRestApi.py +201 -0
- smapi/TrainingDataset.py +299 -0
- smapi/UsersRestApi.py +268 -0
- smapi/Utility.py +273 -0
- smapi/WorkflowExecutionRestApi.py +423 -0
- smapi/WorkflowRestApi.py +571 -0
- smapi/__init__.py +17 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pollen-smart-api
|
|
3
|
+
Version: 3.145.1
|
|
4
|
+
Summary: A Python client for the Pollen Smart 3 platform API
|
|
5
|
+
Author: Pollen Metrology
|
|
6
|
+
Author-email: contact@pollen-metrology.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: Other/Proprietary License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Requires-Dist: numpy <3,>=1.26
|
|
12
|
+
Requires-Dist: opencv-contrib-python-headless <6,>=4.8
|
|
13
|
+
Requires-Dist: pandas <4,>=2.2
|
|
14
|
+
Requires-Dist: jsonschema ~=4.1
|
|
15
|
+
Requires-Dist: typeguard ~=2.13
|
|
16
|
+
Requires-Dist: requests ~=2.26
|
|
17
|
+
Requires-Dist: types-requests ~=2.30
|
|
18
|
+
Requires-Dist: python-dateutil ~=2.8
|
|
19
|
+
Requires-Dist: uritools ~=4.0
|
|
20
|
+
Requires-Dist: websocket-client ~=1.6
|
|
21
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
smapi/AbstractRestApi.py,sha256=xBkP7TuP6_-8lJlDzeKkfE82_NhGGNwGIA85YSAD3vU,9786
|
|
2
|
+
smapi/AnalysisResultsRestApi.py,sha256=y5tYFUZUpNAKMoyKmPUl-EgXtQqMwetcif6SQqaIzwM,13149
|
|
3
|
+
smapi/ChartRestApi.py,sha256=iDR2efg7ZGv0_lNYxEEx-xQVLFnSzOHxVXRJ4QkRb_Y,21949
|
|
4
|
+
smapi/ClassificationRestApi.py,sha256=mVGRbP_OgyxTK6OOR62G2uZi1AqUY17VClWIK9erfIw,12448
|
|
5
|
+
smapi/DataReference.py,sha256=TbeSPb5sCMrGOQKSkovwJJ-9qHEYD4eoe2gD3eY7fDY,4948
|
|
6
|
+
smapi/DataTablesRestApi.py,sha256=9Cx7DhX2YIkYKwfolt--YvLETQCbV3bBLyg36SagGC4,7108
|
|
7
|
+
smapi/EntitiesRestApi.py,sha256=dsDSI6gkY1F1LQ4HvzZvQMJurwrePRok9J0yZmStjdA,21402
|
|
8
|
+
smapi/Error.py,sha256=p_Bm8UPlLT_cyeEW-AZpmw3kx0Eiqi-VyfTmFBnOU_4,3641
|
|
9
|
+
smapi/FigureError.py,sha256=Ng6ZS24mmBi4dbhR4b4iZJ8zUJNU4INqqJowhhYSXto,40607
|
|
10
|
+
smapi/FigureRestApi.py,sha256=uL0G8di44EKIh02mE09fGNR78kaxFv1gNr9l0VqA1oE,101324
|
|
11
|
+
smapi/FileLoadersRestApi.py,sha256=_sG0eKnkV0VLiUnU6Odn2uEOHAiFeXS1fkdhGMBDoBI,7707
|
|
12
|
+
smapi/FilesRestApi.py,sha256=C1-RGTRXIb96Zt9CKx-tM8HpqA4owzvYBtCuUKi6Btk,18594
|
|
13
|
+
smapi/FolderRestApi.py,sha256=1_4QA9eqnBkJCh1BYtUbC_88Rr2Snrs6xe18SuTZ9eQ,13035
|
|
14
|
+
smapi/ImagesRestApi.py,sha256=ntZtWlZ8m-22hPUAOUoZY9TOjt4IDsnI7UbHsM_cZDs,19198
|
|
15
|
+
smapi/LicenseRestApi.py,sha256=H23OUDQ9SroDZdbrOK3vxDVY7tBO1T5901Vhv5cmev0,6305
|
|
16
|
+
smapi/MeasurementResultsRestApi.py,sha256=nb7IfyDXm_9qBE8lDByNgunfl0ad7Yozli0OGpR7x10,10851
|
|
17
|
+
smapi/MeasurementsRestApi.py,sha256=VmlyHwTP2D52YFskVoGBw1t4ypiWpUr0wJdw6zy2QiI,7151
|
|
18
|
+
smapi/NegativeExamples.py,sha256=5vTSw4ij5HjjIrkmFRiCRfur3ndoNhm4MO5MdntWipY,4244
|
|
19
|
+
smapi/PipelineExecutionPreviewsRestApi.py,sha256=8qM9bA5B-trLTW4kU6q0RuEvTAgUikqW4oAwNCi5hgw,2871
|
|
20
|
+
smapi/PipelineExecutionRestApi.py,sha256=1I58CjnRpP9agIa3JJ5rZ-iAvBkbMNNivTMEXb4B1gU,77188
|
|
21
|
+
smapi/PipelineExecutionforErrorTest.py,sha256=gu32-duFhPCvB-gvs_lJrgWHQkfSBzdWusuyYFk5nlU,27266
|
|
22
|
+
smapi/PipelineRestApi.py,sha256=QcB1DI04eSz8PvKhzD6do5Z1iz54esqP9OeNV6zfD5Q,13309
|
|
23
|
+
smapi/PlatformRestApi.py,sha256=sIDMPqh0NaoZU17WXOvrgz0MedkNsF3KbUK23CDp8TU,1853
|
|
24
|
+
smapi/PluginRestApi.py,sha256=qOZ3I22sOXfHNQ4NAvLK5hBTeBV2LeBQVa42RBqppqA,10019
|
|
25
|
+
smapi/RestApi.py,sha256=9M5bX731x0yWn9l1Z2NHIC0JExWlryLZ-MXy1uDJEE4,4761
|
|
26
|
+
smapi/RestSession.py,sha256=D_ZlcXknetUOslGbBOtcyTIdJZa34fe3IO_sVC-HCV8,11056
|
|
27
|
+
smapi/RestWebsocket.py,sha256=3wCXnge3vbRKlvOMuoWwqZvPThwhoznmiKD8IR-lJp8,8974
|
|
28
|
+
smapi/SettingsRestApi.py,sha256=jHX5gdIrN5SUVi48o53dqYlsv6ZvxNozN56IVWzypRk,4766
|
|
29
|
+
smapi/SignalRestApi.py,sha256=sIqdQUNScGlDpcC5reSBp_jne1wIJFa6N_Udr9Op53g,6979
|
|
30
|
+
smapi/TrainingDataset.py,sha256=3p3fmsci6LdQryfBLgO2bhRK8k_Iw_M1CI5sIGEyMMM,11816
|
|
31
|
+
smapi/UsersRestApi.py,sha256=x8Z_Fd4tqE5D_y-AzEMjrxFnV9jOOG_XUZTzFAucJp0,9803
|
|
32
|
+
smapi/Utility.py,sha256=4iCdVzIR0lmn2fmOVQVVHYyozqe-zcX0jwzcRRPWH9I,8751
|
|
33
|
+
smapi/WorkflowExecutionRestApi.py,sha256=5cWRbu3UlOXR3EIwAR7OIdF5qIVdHLBw7xxuO6u5OZk,14511
|
|
34
|
+
smapi/WorkflowRestApi.py,sha256=GQhVSo2bgWHC3L2SvThkxRYf7Zaf98t8Qlxq6YFvnyM,21807
|
|
35
|
+
smapi/__init__.py,sha256=rAMAcRdKP8fMbH1Dpql6iEoxMzaQLf2fyoFJ2K4cd5A,437
|
|
36
|
+
pollen_smart_api-3.145.1.dist-info/METADATA,sha256=NZ6bOJ3ftypWYgFqXLeoY43_O8vrt7SPNeiffd5F1c4,713
|
|
37
|
+
pollen_smart_api-3.145.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
38
|
+
pollen_smart_api-3.145.1.dist-info/top_level.txt,sha256=-SBL2oeVNMe4PovQFzjVboFyQZcHSjsLTQjXyWV2yNg,6
|
|
39
|
+
pollen_smart_api-3.145.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
smapi
|
smapi/AbstractRestApi.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
3
|
+
# Pollen Metrology PUBLIC PYTHON SDK #
|
|
4
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
5
|
+
# [2014-2024] Pollen Metrology
|
|
6
|
+
# All Rights Reserved.
|
|
7
|
+
#
|
|
8
|
+
# NOTICE: All information contained herein is, and remains the property of Pollen Metrology.
|
|
9
|
+
# The intellectual and technical concepts contained herein are proprietary to Pollen Metrology and
|
|
10
|
+
# may be covered by French, European and/or Foreign Patents, patents in process, and are protected
|
|
11
|
+
# by trade secret or copyright law.
|
|
12
|
+
# Dissemination of this information or reproduction of this material is strictly forbidden unless
|
|
13
|
+
# prior written permission is obtained from Pollen Metrology.
|
|
14
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from abc import ABC
|
|
19
|
+
from typing import Dict, Any, Optional, Union
|
|
20
|
+
|
|
21
|
+
import requests as req
|
|
22
|
+
from requests.auth import HTTPBasicAuth
|
|
23
|
+
from typeguard import typechecked
|
|
24
|
+
|
|
25
|
+
from .Error import HttpError
|
|
26
|
+
from .Utility import ConnectionInformation
|
|
27
|
+
|
|
28
|
+
# ================================================================================================= #
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@typechecked
|
|
32
|
+
class AbstractRestApi(ABC):
|
|
33
|
+
"""Base class for all smapi RestApi classes.
|
|
34
|
+
|
|
35
|
+
This class provides access to low-level HTTP communication functions for interacting with Smart3 REST API.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
__slots__ = ("connexion", "apiVersion")
|
|
39
|
+
|
|
40
|
+
@typechecked
|
|
41
|
+
class LowLevel(object):
|
|
42
|
+
"""Low-level HTTP communication"""
|
|
43
|
+
|
|
44
|
+
__slots__ = ("api",)
|
|
45
|
+
|
|
46
|
+
def __init__(self, abstractApi: "AbstractRestApi"):
|
|
47
|
+
self.api: AbstractRestApi = abstractApi
|
|
48
|
+
|
|
49
|
+
def get(self, entrypoint: str) -> Union[bytes, Dict, bool]:
|
|
50
|
+
"""Sends a GET request to the specified entrypoint."""
|
|
51
|
+
return self.api._get(entrypoint)
|
|
52
|
+
|
|
53
|
+
def delete(self, entrypoint: str) -> Union[bytes, Dict, bool]:
|
|
54
|
+
"""Sends a DELETE request to the specified entrypoint."""
|
|
55
|
+
return self.api._delete(entrypoint)
|
|
56
|
+
|
|
57
|
+
def put(self, entrypoint: str, **kwargs: Any) -> Union[bytes, Dict, bool]:
|
|
58
|
+
"""Sends a PUT request to the specified entrypoint with the provided payload."""
|
|
59
|
+
return self.api._put(entrypoint, payload=kwargs)
|
|
60
|
+
|
|
61
|
+
def post(self, entrypoint: str, **kwargs: Any) -> Union[bytes, Dict, bool]:
|
|
62
|
+
"""Sends a POST request to the specified entrypoint with optional payload, files, and parameters."""
|
|
63
|
+
return self.api._post(entrypoint, payload=kwargs)
|
|
64
|
+
|
|
65
|
+
def __init__(self, connexion: ConnectionInformation, apiVersion: str = "api/1.0"):
|
|
66
|
+
#: Current connection information including session ID and authentication details.
|
|
67
|
+
self.connexion: ConnectionInformation = connexion
|
|
68
|
+
#: Version of the REST API being accessed, default is "api/1.0".
|
|
69
|
+
self.apiVersion: str = apiVersion
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def __auth_cookie(self) -> Dict[str, str]:
|
|
73
|
+
return dict(sessionId=str(self.connexion.session_id))
|
|
74
|
+
|
|
75
|
+
def __url(self, entrypoint: str) -> str:
|
|
76
|
+
return f"{self.connexion.root_uri}/{self.apiVersion}/{entrypoint}"
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def ll(self) -> LowLevel:
|
|
80
|
+
return AbstractRestApi.LowLevel(self)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def __check_and_return(
|
|
84
|
+
response: req.Response, as_binary: bool = False
|
|
85
|
+
) -> Union[bytes, Dict, bool]:
|
|
86
|
+
if response.status_code != req.codes.ok:
|
|
87
|
+
raise HttpError(response)
|
|
88
|
+
if as_binary:
|
|
89
|
+
return response.content
|
|
90
|
+
if len(response.content) == 0:
|
|
91
|
+
return True
|
|
92
|
+
return response.json()
|
|
93
|
+
|
|
94
|
+
def _get(self, entrypoint: str, params: Optional[Dict] = None) -> Union[bytes, Dict, bool]:
|
|
95
|
+
result: req.Response
|
|
96
|
+
if self.connexion.session_id is not None:
|
|
97
|
+
result = req.get(
|
|
98
|
+
url=self.__url(entrypoint),
|
|
99
|
+
params=params or dict(),
|
|
100
|
+
cookies=self.__auth_cookie,
|
|
101
|
+
timeout=self.connexion.timeout,
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
result = req.get(
|
|
105
|
+
url=self.__url(entrypoint),
|
|
106
|
+
params=params or dict(),
|
|
107
|
+
auth=HTTPBasicAuth(
|
|
108
|
+
self.connexion.basic_auth_username,
|
|
109
|
+
self.connexion.basic_auth_password,
|
|
110
|
+
),
|
|
111
|
+
timeout=self.connexion.timeout,
|
|
112
|
+
)
|
|
113
|
+
return AbstractRestApi.__check_and_return(result)
|
|
114
|
+
|
|
115
|
+
def _get_binary(self, entrypoint: str) -> Union[bytes, Dict, bool]:
|
|
116
|
+
result: req.Response
|
|
117
|
+
if self.connexion.session_id is not None:
|
|
118
|
+
result = req.get(
|
|
119
|
+
url=self.__url(entrypoint),
|
|
120
|
+
cookies=self.__auth_cookie,
|
|
121
|
+
timeout=self.connexion.timeout,
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
result = req.get(
|
|
125
|
+
url=self.__url(entrypoint),
|
|
126
|
+
auth=HTTPBasicAuth(
|
|
127
|
+
self.connexion.basic_auth_username,
|
|
128
|
+
self.connexion.basic_auth_password,
|
|
129
|
+
),
|
|
130
|
+
timeout=self.connexion.timeout,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return AbstractRestApi.__check_and_return(result, as_binary=True)
|
|
134
|
+
|
|
135
|
+
def _post_and_return_binary(
|
|
136
|
+
self, entrypoint: str, payload: Optional[Dict] = None
|
|
137
|
+
) -> Union[bytes, Dict, bool]:
|
|
138
|
+
result: req.Response
|
|
139
|
+
if self.connexion.session_id is not None:
|
|
140
|
+
result = req.post(
|
|
141
|
+
url=self.__url(entrypoint),
|
|
142
|
+
json=payload,
|
|
143
|
+
cookies=self.__auth_cookie,
|
|
144
|
+
timeout=self.connexion.timeout,
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
result = req.post(
|
|
148
|
+
url=self.__url(entrypoint),
|
|
149
|
+
json=payload,
|
|
150
|
+
auth=HTTPBasicAuth(
|
|
151
|
+
self.connexion.basic_auth_username,
|
|
152
|
+
self.connexion.basic_auth_password,
|
|
153
|
+
),
|
|
154
|
+
timeout=self.connexion.timeout,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return AbstractRestApi.__check_and_return(result, as_binary=True)
|
|
158
|
+
|
|
159
|
+
def _post(
|
|
160
|
+
self,
|
|
161
|
+
entrypoint: str,
|
|
162
|
+
payload: Optional[Dict] = None,
|
|
163
|
+
files: Optional[Dict] = None,
|
|
164
|
+
parameters: Optional[Dict] = None,
|
|
165
|
+
) -> Union[bytes, Dict, bool]:
|
|
166
|
+
result: req.Response
|
|
167
|
+
if self.connexion.session_id is not None:
|
|
168
|
+
result = req.post(
|
|
169
|
+
url=self.__url(entrypoint),
|
|
170
|
+
json=payload,
|
|
171
|
+
files=files,
|
|
172
|
+
params=parameters,
|
|
173
|
+
cookies=self.__auth_cookie,
|
|
174
|
+
timeout=self.connexion.timeout,
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
result = req.post(
|
|
178
|
+
url=self.__url(entrypoint),
|
|
179
|
+
json=payload,
|
|
180
|
+
files=files,
|
|
181
|
+
params=parameters,
|
|
182
|
+
auth=HTTPBasicAuth(
|
|
183
|
+
self.connexion.basic_auth_username,
|
|
184
|
+
self.connexion.basic_auth_password,
|
|
185
|
+
),
|
|
186
|
+
timeout=self.connexion.timeout,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return AbstractRestApi.__check_and_return(result)
|
|
190
|
+
|
|
191
|
+
def _put(
|
|
192
|
+
self, entrypoint: str, payload: Dict, parameters: Optional[Dict] = None
|
|
193
|
+
) -> Union[bytes, Dict, bool]:
|
|
194
|
+
result: req.Response
|
|
195
|
+
if self.connexion.session_id is not None:
|
|
196
|
+
result = req.put(
|
|
197
|
+
url=self.__url(entrypoint),
|
|
198
|
+
json=payload,
|
|
199
|
+
params=parameters,
|
|
200
|
+
cookies=self.__auth_cookie,
|
|
201
|
+
timeout=self.connexion.timeout,
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
result = req.put(
|
|
205
|
+
url=self.__url(entrypoint),
|
|
206
|
+
json=payload,
|
|
207
|
+
params=parameters,
|
|
208
|
+
auth=HTTPBasicAuth(
|
|
209
|
+
self.connexion.basic_auth_username,
|
|
210
|
+
self.connexion.basic_auth_password,
|
|
211
|
+
),
|
|
212
|
+
timeout=self.connexion.timeout,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return AbstractRestApi.__check_and_return(result)
|
|
216
|
+
|
|
217
|
+
def _delete(self, entrypoint: str, payload: Optional[Dict] = None) -> Union[bytes, Dict, bool]:
|
|
218
|
+
result: req.Response
|
|
219
|
+
if payload is None:
|
|
220
|
+
if self.connexion.session_id is not None:
|
|
221
|
+
result = req.delete(
|
|
222
|
+
url=self.__url(entrypoint),
|
|
223
|
+
cookies=self.__auth_cookie,
|
|
224
|
+
timeout=self.connexion.timeout,
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
result = req.delete(
|
|
228
|
+
url=self.__url(entrypoint),
|
|
229
|
+
auth=HTTPBasicAuth(
|
|
230
|
+
self.connexion.basic_auth_username,
|
|
231
|
+
self.connexion.basic_auth_password,
|
|
232
|
+
),
|
|
233
|
+
timeout=self.connexion.timeout,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return AbstractRestApi.__check_and_return(result)
|
|
237
|
+
|
|
238
|
+
else:
|
|
239
|
+
if self.connexion.session_id is not None:
|
|
240
|
+
result = req.delete(
|
|
241
|
+
url=self.__url(entrypoint),
|
|
242
|
+
json=payload,
|
|
243
|
+
cookies=self.__auth_cookie,
|
|
244
|
+
timeout=self.connexion.timeout,
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
result = req.delete(
|
|
248
|
+
url=self.__url(entrypoint),
|
|
249
|
+
json=payload,
|
|
250
|
+
auth=HTTPBasicAuth(
|
|
251
|
+
self.connexion.basic_auth_username,
|
|
252
|
+
self.connexion.basic_auth_password,
|
|
253
|
+
),
|
|
254
|
+
timeout=self.connexion.timeout,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return AbstractRestApi.__check_and_return(result)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ================================================================================================= #
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
3
|
+
# Pollen Metrology PUBLIC PYTHON SDK #
|
|
4
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
5
|
+
# [2014-2024] Pollen Metrology
|
|
6
|
+
# All Rights Reserved.
|
|
7
|
+
#
|
|
8
|
+
# NOTICE: All information contained herein is, and remains the property of Pollen Metrology.
|
|
9
|
+
# The intellectual and technical concepts contained herein are proprietary to Pollen Metrology and
|
|
10
|
+
# may be covered by French, European and/or Foreign Patents, patents in process, and are protected
|
|
11
|
+
# by trade secret or copyright law.
|
|
12
|
+
# Dissemination of this information or reproduction of this material is strictly forbidden unless
|
|
13
|
+
# prior written permission is obtained from Pollen Metrology.
|
|
14
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
15
|
+
|
|
16
|
+
from typing import List, Dict, Union, Optional
|
|
17
|
+
from uuid import UUID
|
|
18
|
+
|
|
19
|
+
from typeguard import typechecked
|
|
20
|
+
|
|
21
|
+
from .AbstractRestApi import AbstractRestApi
|
|
22
|
+
from .ChartRestApi import ChartInformation
|
|
23
|
+
from .DataReference import DataReference
|
|
24
|
+
|
|
25
|
+
# ================================================================================================= #
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@typechecked
|
|
29
|
+
class Analysis(DataReference):
|
|
30
|
+
"""Analysis result.
|
|
31
|
+
|
|
32
|
+
This class represents an analysis result, including entities, relationships, masks,
|
|
33
|
+
messages, tables, charts, and metadata. It provides methods to compare analysis results and
|
|
34
|
+
to create instances from dictionaries.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
__slots__ = (
|
|
38
|
+
"entities",
|
|
39
|
+
"relationships",
|
|
40
|
+
"masks",
|
|
41
|
+
"messages",
|
|
42
|
+
"tables",
|
|
43
|
+
"charts",
|
|
44
|
+
"metadata",
|
|
45
|
+
"created_at",
|
|
46
|
+
"updated_at",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def __init__(self, attributes: Dict, relationships: Dict, identifier: Optional[str] = None):
|
|
50
|
+
"""Initializes the Analysis object with attributes and relationships.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
attributes (Dict): A dictionary containing attributes of the analysis.
|
|
54
|
+
relationships (Dict): A dictionary containing relationships of the analysis.
|
|
55
|
+
identifier (Optional[str]): An optional identifier for the analysis.
|
|
56
|
+
"""
|
|
57
|
+
super(Analysis, self).__init__(identifier)
|
|
58
|
+
|
|
59
|
+
#: List of entities in the analysis results.
|
|
60
|
+
self.entities: Optional[List[Dict]] = attributes.get("entities", [])
|
|
61
|
+
#: List of image masks in the analysis results.
|
|
62
|
+
self.masks: Optional[Dict] = attributes.get("masks")
|
|
63
|
+
#: List of messages displayed in the bottom panel for the analysis results.
|
|
64
|
+
self.messages: Optional[List[Dict]] = attributes.get("messages")
|
|
65
|
+
#: Relationships of the analysis results. Can be identifiers of image or folder.
|
|
66
|
+
self.relationships: Optional[Dict] = relationships.get("id")
|
|
67
|
+
#: List of tables in the analysis results.
|
|
68
|
+
self.tables: Optional[List[Dict]] = attributes.get("tables")
|
|
69
|
+
#: List of charts in the analysis results.
|
|
70
|
+
self.charts: List[ChartInformation] = [
|
|
71
|
+
ChartInformation(**c) for c in attributes.get("charts") or list()
|
|
72
|
+
]
|
|
73
|
+
#: Additional metadata related to the analysis results.
|
|
74
|
+
self.metadata: Optional[List[Dict]] = attributes.get("metadata")
|
|
75
|
+
#: Timestamp when the analysis result was created (ISO 8601 format).
|
|
76
|
+
self.created_at: Optional[str] = attributes.get("createdAt")
|
|
77
|
+
#: Timestamp when the analysis result was last updated (ISO 8601 format).
|
|
78
|
+
self.updated_at: Optional[str] = attributes.get("updatedAt")
|
|
79
|
+
|
|
80
|
+
def __repr__(self) -> str:
|
|
81
|
+
return str(self.identifier)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def data_from_dictionary_internal(dictionary: Dict) -> "Analysis":
|
|
85
|
+
if "id" not in dictionary or "attributes" not in dictionary:
|
|
86
|
+
raise RuntimeError("Unable to create the analysis result information !")
|
|
87
|
+
|
|
88
|
+
return Analysis(
|
|
89
|
+
identifier=dictionary.get("id"),
|
|
90
|
+
attributes=dictionary.get("attributes"),
|
|
91
|
+
relationships=dictionary.get("relationships"),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def measurements_equivalent(
|
|
95
|
+
self, meas0: List[dict], meas1: List[dict], uuidMap: Dict[UUID, UUID]
|
|
96
|
+
) -> bool:
|
|
97
|
+
"""Compares two sets of measurements for equivalence.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
meas0 (List[dict]): The first set of measurements.
|
|
101
|
+
meas1 (List[dict]): The second set of measurements.
|
|
102
|
+
uuidMap (Dict[UUID, UUID]): A mapping of UUIDs for comparison.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool: True if the measurements are equivalent, False otherwise.
|
|
106
|
+
"""
|
|
107
|
+
for m0, m1 in zip(meas0, meas1):
|
|
108
|
+
if set(m0.keys()) != set(m1.keys()):
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
for k, v in m0.items():
|
|
112
|
+
if k == "value" and v is not None:
|
|
113
|
+
if isinstance(v, float) or isinstance(v, int):
|
|
114
|
+
if round(v, 9) != round(m1[k], 9):
|
|
115
|
+
return False
|
|
116
|
+
elif isinstance(v, dict) and "x" in v and "y" in v:
|
|
117
|
+
g_value: bool = (
|
|
118
|
+
round(v["x"][0]),
|
|
119
|
+
round(v["x"][1]),
|
|
120
|
+
round(v["y"][0]),
|
|
121
|
+
round(v["y"][1]),
|
|
122
|
+
) == (
|
|
123
|
+
round(m1[k]["x"][0]),
|
|
124
|
+
round(m1[k]["x"][1]),
|
|
125
|
+
round(m1[k]["y"][0]),
|
|
126
|
+
round(m1[k]["y"][1]),
|
|
127
|
+
)
|
|
128
|
+
if not g_value:
|
|
129
|
+
return False
|
|
130
|
+
elif k == "id" or k == "geometry1Id" or k == "geometry2Id":
|
|
131
|
+
if uuidMap[UUID(m0[k])] != UUID(m1[k]):
|
|
132
|
+
return False
|
|
133
|
+
elif k == "values":
|
|
134
|
+
self.measurements_equivalent(v, m1[k], uuidMap)
|
|
135
|
+
else:
|
|
136
|
+
if v != m1[k]:
|
|
137
|
+
return False
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
def equivalent(self, other, uuidMap: Dict[UUID, UUID]) -> bool:
|
|
141
|
+
"""Checks if two Analysis objects are equivalent.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
other (Analysis): Another Analysis object to compare.
|
|
145
|
+
uuidMap (Dict[UUID, UUID]): A mapping of UUIDs for comparison.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
bool: True if the two Analysis objects are equivalent, False otherwise.
|
|
149
|
+
"""
|
|
150
|
+
if self.entities or other.entities:
|
|
151
|
+
for e0, e1 in zip(self.entities, other.entities):
|
|
152
|
+
for k, _ in e0.items():
|
|
153
|
+
if k == "id" and e0[k] == e1[k]:
|
|
154
|
+
return False
|
|
155
|
+
elif k == "classifications" and e0[k] is not None and e0[k] == e1[k]:
|
|
156
|
+
return False
|
|
157
|
+
elif k == "figures":
|
|
158
|
+
for f0, f1 in zip(e0[k], e1[k]):
|
|
159
|
+
equivalent: bool = (
|
|
160
|
+
f0["controlPoints"] == f1["controlPoints"]
|
|
161
|
+
and f0.get("metadata", dict()) == f1.get("metadata", dict())
|
|
162
|
+
and f0["name"] == f1["name"]
|
|
163
|
+
and f0["type"] == f1["type"]
|
|
164
|
+
and f0["id"] != f1["id"]
|
|
165
|
+
)
|
|
166
|
+
if not equivalent:
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
if f0.get("measurements") or f1.get("measurements"):
|
|
170
|
+
equivalent: bool = self.measurements_equivalent(
|
|
171
|
+
f0.get("measurements"), f1.get("measurements"), uuidMap
|
|
172
|
+
)
|
|
173
|
+
if not equivalent:
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
elif k not in ("id", "figures", "classifications") and e0[k] != e1[k]:
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
if self.masks:
|
|
180
|
+
if not other.masks:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
for k, v in self.masks.items():
|
|
184
|
+
if isinstance(v, str) and v == other.masks[k]:
|
|
185
|
+
return False
|
|
186
|
+
elif not isinstance(v, str) and self.masks[k] != other.masks[k]:
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
gdata: bool = (
|
|
190
|
+
self.messages,
|
|
191
|
+
self.metadata,
|
|
192
|
+
self.tables,
|
|
193
|
+
) == (
|
|
194
|
+
other.messages,
|
|
195
|
+
other.metadata,
|
|
196
|
+
other.tables,
|
|
197
|
+
)
|
|
198
|
+
if not gdata:
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
for ch0, ch1 in zip(self.charts, other.charts):
|
|
202
|
+
if not ch0.equivalent(ch1, uuidMap):
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
return self.identifier != other.identifier
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def type(cls: type) -> str:
|
|
209
|
+
"""Return the string "AnalysisResults"."""
|
|
210
|
+
return "AnalysisResults"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
214
|
+
# ------------------------------------------------------------------------------------------------- #
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@typechecked
|
|
218
|
+
class AnalysisResultsRestApi(AbstractRestApi):
|
|
219
|
+
"""AnalysisResults REST API.
|
|
220
|
+
|
|
221
|
+
This class provides methods to interact with the AnalysisResults REST API, allowing
|
|
222
|
+
retrieval, deletion, and update of analysis results.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def all(self) -> List[Analysis]:
|
|
226
|
+
"""Get all analysis results.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List[Analysis]: The list of all analysis results.
|
|
230
|
+
"""
|
|
231
|
+
data: Dict = self._get(entrypoint="analysisResults")
|
|
232
|
+
return Analysis.data_from_dictionary(data)
|
|
233
|
+
|
|
234
|
+
def get(self, identifier: Union[UUID, str]) -> Analysis:
|
|
235
|
+
"""Get a specific analysis result.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
identifier (Union[UUID, str]): The identifier of the analysis result to retrieve.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Analysis: The requested analysis result.
|
|
242
|
+
"""
|
|
243
|
+
data: Dict = self._get(entrypoint=f"analysisResults/{str(identifier)}")
|
|
244
|
+
return Analysis.data_from_dictionary(data)
|
|
245
|
+
|
|
246
|
+
def get_for_image(self, image_identifier: Union[UUID, str]) -> List[Analysis]:
|
|
247
|
+
"""Get all analysis results for an image.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
image_identifier (Union[UUID, str]): The identifier of the image.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
List[Analysis]: A list of analysis results related to the specified image.
|
|
254
|
+
"""
|
|
255
|
+
data: Dict = self._get(entrypoint=f"analysisResults?filter[image]={str(image_identifier)}")
|
|
256
|
+
return Analysis.data_from_dictionary(data)
|
|
257
|
+
|
|
258
|
+
def get_for_folder(self, folder_identifier: Union[UUID, str]) -> List[Analysis]:
|
|
259
|
+
"""Get all analysis results for a folder.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
folder_identifier (Union[UUID, str]): The identifier of the folder.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List[Analysis]: A list of analysis results related to the specified folder.
|
|
266
|
+
"""
|
|
267
|
+
data: Dict = self._get(entrypoint=f"analysisResults?filter[folder]={str(folder_identifier)}")
|
|
268
|
+
return Analysis.data_from_dictionary(data)
|
|
269
|
+
|
|
270
|
+
def get_for_input(self, identifiers: List[Union[UUID, str]]) -> List[Analysis]:
|
|
271
|
+
"""Get the analysis results related to the list of inputs given in parameters.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
identifiers (List[Union[UUID, str]]): A list of input identifiers.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
List[Analysis]: A list of analysis results related to the specified inputs.
|
|
278
|
+
"""
|
|
279
|
+
data: Dict = self._get(
|
|
280
|
+
entrypoint=f"analysisResults?filter[*]={','.join([str(identifier) for identifier in identifiers])}"
|
|
281
|
+
)
|
|
282
|
+
return Analysis.data_from_dictionary(data)
|
|
283
|
+
|
|
284
|
+
def delete(self, identifiers: List[Union[UUID, str]]) -> List[DataReference]:
|
|
285
|
+
"""Delete a specific analysis result.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
identifiers (List[Union[UUID, str]]): A list of identifiers for the analysis results to delete.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
List[DataReference]: A list of references to the deleted analysis results.
|
|
292
|
+
"""
|
|
293
|
+
payload: Dict = dict(
|
|
294
|
+
data=[dict(id=str(identifier), type=Analysis.type()) for identifier in identifiers]
|
|
295
|
+
)
|
|
296
|
+
data: Dict = self._delete(entrypoint="analysisResults", payload=payload)
|
|
297
|
+
return Analysis.reference_from_dictionary(data)
|
|
298
|
+
|
|
299
|
+
def update(self, analysis_results: List[Analysis]) -> List[Analysis]:
|
|
300
|
+
"""Update metadata of analysis results.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
analysis_results (List[Analysis]): A list of Analysis objects to update.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List[Analysis]: A list of updated Analysis objects.
|
|
307
|
+
"""
|
|
308
|
+
payload: Dict = dict(
|
|
309
|
+
data=[
|
|
310
|
+
dict(
|
|
311
|
+
id=str(analysis_result.identifier),
|
|
312
|
+
type=Analysis.type(),
|
|
313
|
+
attributes=dict(metadata=analysis_result.metadata),
|
|
314
|
+
)
|
|
315
|
+
for analysis_result in analysis_results
|
|
316
|
+
]
|
|
317
|
+
)
|
|
318
|
+
data: Dict = self._put(entrypoint="analysisResults", payload=payload)
|
|
319
|
+
return Analysis.data_from_dictionary(data)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# ================================================================================================= #
|