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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ smapi
@@ -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
+ # ================================================================================================= #