medpython 1.2.0__cp314-cp314-win_amd64.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.
Potentially problematic release.
This version of medpython might be problematic. Click here for more details.
- AlgoMarker/AlgoMarker.py +872 -0
- AlgoMarker/__init__.py +12 -0
- med/Release/_medpython.pyd +0 -0
- med/__init__.py +12 -0
- med/_medpython.pyd +0 -0
- med/med.py +4 -0
- med/medpython.py +3995 -0
- medpython-1.2.0.dist-info/DELVEWHEEL +2 -0
- medpython-1.2.0.dist-info/METADATA +201 -0
- medpython-1.2.0.dist-info/RECORD +16 -0
- medpython-1.2.0.dist-info/WHEEL +5 -0
- medpython-1.2.0.dist-info/licenses/LICENCE +21 -0
- medpython-1.2.0.dist-info/top_level.txt +2 -0
- medpython.libs/concrt140-b5f14f08cfeabe717a4945429e207076.dll +0 -0
- medpython.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
- medpython.libs/vcomp140-f96f3a14d88d8846f31f3ab38a490304.dll +0 -0
AlgoMarker/AlgoMarker.py
ADDED
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
import ctypes, json, traceback, os
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Any, List, Callable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SingleDataElement:
|
|
7
|
+
"""SingleDataElement object that holds a single data element in AlgoMarker patient data repository."""
|
|
8
|
+
|
|
9
|
+
times: List[int]
|
|
10
|
+
values: List[float]
|
|
11
|
+
|
|
12
|
+
def __init__(self, times: List[int], values: List[float]):
|
|
13
|
+
"""SingleDataElement constructor - receives signal name, times and values"""
|
|
14
|
+
self.times = times
|
|
15
|
+
self.values = values
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return f"(times={self.times}, values={self.values})"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AlgoMarker:
|
|
22
|
+
"""AlgoMarker object that holds full model pipeline to calculate meaningfull insights from EMR raw data.
|
|
23
|
+
|
|
24
|
+
Methods
|
|
25
|
+
-------
|
|
26
|
+
calculate
|
|
27
|
+
recieves a request for execution of the model pipeline and returns a responde
|
|
28
|
+
discovery
|
|
29
|
+
returns a json specification of the AlgoMarker information, inputs, etc.
|
|
30
|
+
dispose
|
|
31
|
+
Release object memory - recomanded to use "with" statement
|
|
32
|
+
clear_data
|
|
33
|
+
clears AlgoMarker patient data repository memory
|
|
34
|
+
add_data
|
|
35
|
+
loads the AlgoMarker patient data repository memory with patient data
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __test_not_disposed(func: Callable) -> Callable:
|
|
39
|
+
@wraps(func)
|
|
40
|
+
def wrapper(*args):
|
|
41
|
+
s_obj = args[0]
|
|
42
|
+
if s_obj.__disposed:
|
|
43
|
+
raise NameError(
|
|
44
|
+
f"Error - Can't call {func.__name__} after algomarker was disposed"
|
|
45
|
+
)
|
|
46
|
+
return func(*args)
|
|
47
|
+
|
|
48
|
+
return wrapper
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def __load_am_lib(libpath: str) -> tuple[ctypes.CDLL, int]:
|
|
52
|
+
api_level = 2
|
|
53
|
+
# Load the shared library into ctypes
|
|
54
|
+
c_lib = ctypes.CDLL(libpath)
|
|
55
|
+
c_lib.AM_API_Create.argtypes = (ctypes.c_int32, ctypes.POINTER(ctypes.c_void_p))
|
|
56
|
+
c_lib.AM_API_Load.argtypes = (ctypes.c_void_p, ctypes.POINTER(ctypes.c_char))
|
|
57
|
+
c_lib.AM_API_Load.restype = ctypes.c_int32
|
|
58
|
+
c_lib.AM_API_DisposeAlgoMarker.argtypes = [ctypes.c_void_p]
|
|
59
|
+
c_lib.AM_API_DisposeAlgoMarker.restype = None
|
|
60
|
+
c_lib.AM_API_ClearData.argtypes = [ctypes.c_void_p]
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
hasattr(c_lib, "AM_API_AddDataByType")
|
|
64
|
+
and hasattr(c_lib, "AM_API_CalculateByType")
|
|
65
|
+
and hasattr(c_lib, "AM_API_Discovery")
|
|
66
|
+
):
|
|
67
|
+
c_lib.AM_API_Discovery.argtypes = (
|
|
68
|
+
ctypes.c_void_p,
|
|
69
|
+
ctypes.POINTER(ctypes.c_char_p),
|
|
70
|
+
)
|
|
71
|
+
c_lib.AM_API_Discovery.restype = None
|
|
72
|
+
c_lib.AM_API_AddDataByType.argtypes = (
|
|
73
|
+
ctypes.c_void_p,
|
|
74
|
+
ctypes.c_char_p,
|
|
75
|
+
ctypes.POINTER(ctypes.c_char_p),
|
|
76
|
+
)
|
|
77
|
+
c_lib.AM_API_CalculateByType.argtypes = (
|
|
78
|
+
ctypes.c_void_p,
|
|
79
|
+
ctypes.c_int32,
|
|
80
|
+
ctypes.c_char_p,
|
|
81
|
+
ctypes.POINTER(ctypes.c_char_p),
|
|
82
|
+
)
|
|
83
|
+
c_lib.AM_API_Dispose.argtypes = [ctypes.c_char_p]
|
|
84
|
+
c_lib.AM_API_Dispose.restype = None
|
|
85
|
+
else:
|
|
86
|
+
print(
|
|
87
|
+
"Warning: AM_API_AddDataByType or AM_API_CalculateByType not found in the library, using old API"
|
|
88
|
+
)
|
|
89
|
+
api_level = 1
|
|
90
|
+
|
|
91
|
+
c_lib.AM_API_AddData.argtypes = (
|
|
92
|
+
ctypes.c_void_p,
|
|
93
|
+
ctypes.c_int32,
|
|
94
|
+
ctypes.POINTER(ctypes.c_char),
|
|
95
|
+
ctypes.c_int32,
|
|
96
|
+
ctypes.POINTER(ctypes.c_long),
|
|
97
|
+
ctypes.c_int32,
|
|
98
|
+
ctypes.POINTER(ctypes.c_float),
|
|
99
|
+
)
|
|
100
|
+
c_lib.AM_API_AddData.restype = ctypes.c_int32
|
|
101
|
+
c_lib.AM_API_GetName.argtypes = (
|
|
102
|
+
ctypes.c_void_p,
|
|
103
|
+
ctypes.POINTER(ctypes.c_char_p),
|
|
104
|
+
)
|
|
105
|
+
c_lib.AM_API_GetName.restype = None
|
|
106
|
+
|
|
107
|
+
c_lib.AM_API_CreateRequest.argtypes = (
|
|
108
|
+
ctypes.c_char_p, # Request type
|
|
109
|
+
ctypes.POINTER(ctypes.c_char_p), # Score Type
|
|
110
|
+
ctypes.c_int32,
|
|
111
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
112
|
+
ctypes.POINTER(ctypes.c_long),
|
|
113
|
+
ctypes.c_int32,
|
|
114
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
115
|
+
)
|
|
116
|
+
c_lib.AM_API_CreateRequest.restype = ctypes.c_int32
|
|
117
|
+
c_lib.AM_API_CreateResponses.argtypes = (
|
|
118
|
+
ctypes.POINTER(ctypes.c_void_p), # AlgoMarker object
|
|
119
|
+
) # Request type
|
|
120
|
+
c_lib.AM_API_CreateResponses.restype = None
|
|
121
|
+
c_lib.AM_API_DisposeRequest.argtypes = (ctypes.c_void_p,) # Request object
|
|
122
|
+
c_lib.AM_API_DisposeRequest.restype = None
|
|
123
|
+
c_lib.AM_API_DisposeResponses.argtypes = (ctypes.c_void_p,) # Response object
|
|
124
|
+
c_lib.AM_API_DisposeResponses.restype = None
|
|
125
|
+
c_lib.AM_API_Calculate.argtypes = (
|
|
126
|
+
ctypes.c_void_p, # AlgoMarker object
|
|
127
|
+
ctypes.c_void_p, # Request object
|
|
128
|
+
ctypes.c_void_p, # Response json string
|
|
129
|
+
)
|
|
130
|
+
c_lib.AM_API_Calculate.restype = ctypes.c_int32
|
|
131
|
+
|
|
132
|
+
c_lib.AM_API_GetResponsesNum.argtypes = (ctypes.c_void_p,)
|
|
133
|
+
c_lib.AM_API_GetResponsesNum.restype = ctypes.c_int32
|
|
134
|
+
|
|
135
|
+
c_lib.AM_API_GetResponseAtIndex.argtypes = (
|
|
136
|
+
ctypes.c_void_p,
|
|
137
|
+
ctypes.c_int32,
|
|
138
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
139
|
+
)
|
|
140
|
+
c_lib.AM_API_GetResponseAtIndex.restype = ctypes.c_int32
|
|
141
|
+
|
|
142
|
+
c_lib.AM_API_GetResponseScoresNum.argtypes = (
|
|
143
|
+
ctypes.c_void_p,
|
|
144
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
145
|
+
)
|
|
146
|
+
c_lib.AM_API_GetResponseScoresNum.restype = ctypes.c_int32
|
|
147
|
+
|
|
148
|
+
c_lib.AM_API_GetResponsePoint.argtypes = (
|
|
149
|
+
ctypes.c_void_p, # Response object
|
|
150
|
+
ctypes.POINTER(ctypes.c_int32), # Patient ID
|
|
151
|
+
ctypes.POINTER(ctypes.c_long), # Timestamp
|
|
152
|
+
)
|
|
153
|
+
c_lib.AM_API_GetResponsePoint.restype = ctypes.c_int32
|
|
154
|
+
|
|
155
|
+
c_lib.AM_API_GetResponseMessages.argtypes = (
|
|
156
|
+
ctypes.c_void_p, # Response object
|
|
157
|
+
ctypes.POINTER(ctypes.c_int32), # Number of messages
|
|
158
|
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_int32)), # Message codes
|
|
159
|
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)), # Messages errors
|
|
160
|
+
)
|
|
161
|
+
c_lib.AM_API_GetResponseMessages.restype = ctypes.c_int32
|
|
162
|
+
|
|
163
|
+
c_lib.AM_API_GetScoreMessages.argtypes = (
|
|
164
|
+
ctypes.c_void_p, # Response object
|
|
165
|
+
ctypes.c_int32, # score_index
|
|
166
|
+
ctypes.POINTER(ctypes.c_int32), # Number of messages
|
|
167
|
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_int32)), # Message codes
|
|
168
|
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)), # Messages errors
|
|
169
|
+
)
|
|
170
|
+
c_lib.AM_API_GetScoreMessages.restype = ctypes.c_int32
|
|
171
|
+
|
|
172
|
+
c_lib.AM_API_GetResponseScoreByIndex.argtypes = (
|
|
173
|
+
ctypes.c_void_p, # Response object
|
|
174
|
+
ctypes.c_int32, # score_index
|
|
175
|
+
ctypes.POINTER(ctypes.c_float), # Score value
|
|
176
|
+
ctypes.POINTER(ctypes.c_char_p), # Score type
|
|
177
|
+
)
|
|
178
|
+
c_lib.AM_API_GetResponseScoreByIndex.restype = ctypes.c_int32
|
|
179
|
+
|
|
180
|
+
c_lib.AM_API_GetSharedMessages.argtypes = (
|
|
181
|
+
ctypes.c_void_p, # Response object
|
|
182
|
+
ctypes.POINTER(ctypes.c_int32), # Number of messages
|
|
183
|
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_int32)), # Message codes
|
|
184
|
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)), # Messages errors
|
|
185
|
+
)
|
|
186
|
+
c_lib.AM_API_GetSharedMessages.restype = ctypes.c_int32
|
|
187
|
+
|
|
188
|
+
return c_lib, api_level
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def create_request_json(patient_id: int, prediction_time: int) -> str:
|
|
192
|
+
"""Creates and returns a string json request for patient_id and prediction_time"""
|
|
193
|
+
js_req = (
|
|
194
|
+
'{"type": "request", "request_id": "REQ_ID_1234", '
|
|
195
|
+
+ '"export": {"prediction": "pred_0"}, "requests": [ '
|
|
196
|
+
+ '{"patient_id":"%d", "time": "%d"} ]}'
|
|
197
|
+
% (int(patient_id), int(prediction_time))
|
|
198
|
+
)
|
|
199
|
+
return js_req
|
|
200
|
+
|
|
201
|
+
def __init__(self, amconfig_path: str, libpath: str | None = None):
|
|
202
|
+
"""AlgoMarker constractor - receives AlgoMarker configuration file path "amconfig".
|
|
203
|
+
Optional path to C shared library file. If we want to use other version, not default
|
|
204
|
+
library that is packed in this module.
|
|
205
|
+
"""
|
|
206
|
+
if libpath is None:
|
|
207
|
+
libpath = os.path.join(
|
|
208
|
+
os.path.dirname(os.path.abspath(__file__)), "libdyn_AlgoMarker.so"
|
|
209
|
+
)
|
|
210
|
+
self.__lib = None
|
|
211
|
+
self.__lib, self.api_version = AlgoMarker.__load_am_lib(libpath)
|
|
212
|
+
self.__libpath = libpath
|
|
213
|
+
print(f"Loaded library from {self.__libpath}")
|
|
214
|
+
self.__obj = ctypes.c_void_p()
|
|
215
|
+
res = self.__lib.AM_API_Create(1, ctypes.pointer(self.__obj))
|
|
216
|
+
if res != 0:
|
|
217
|
+
print("Error in creating AlgoMarker object")
|
|
218
|
+
self.__disposed = False
|
|
219
|
+
self.__name = None
|
|
220
|
+
self.__amconfig_path = amconfig_path
|
|
221
|
+
self.__load_algomarker(amconfig_path)
|
|
222
|
+
self.__discovery_full = None
|
|
223
|
+
|
|
224
|
+
def __parse_config_amfile(self):
|
|
225
|
+
if self.__discovery_full is not None:
|
|
226
|
+
return self.__discovery_full
|
|
227
|
+
self.__discovery_full = self.get_name()
|
|
228
|
+
self.__discovery_full["version"] = ""
|
|
229
|
+
with open(self.__amconfig_path, "r") as f:
|
|
230
|
+
lines = f.readlines()
|
|
231
|
+
lines = list(
|
|
232
|
+
filter(lambda x: x.strip() != "" or not (x.strip().startswith("#")), lines)
|
|
233
|
+
)
|
|
234
|
+
rep_path = list(filter(lambda x: x.startswith("REPOSITORY\t"),lines))
|
|
235
|
+
if len(rep_path)==1:
|
|
236
|
+
rep_path = rep_path[0].split("\t")[1].strip()
|
|
237
|
+
# Read rep_path - relative to current amconfig:
|
|
238
|
+
if not os.path.isabs(rep_path):
|
|
239
|
+
rep_path = os.path.join(os.path.dirname(self.__amconfig_path), rep_path)
|
|
240
|
+
else:
|
|
241
|
+
rep_path = None
|
|
242
|
+
|
|
243
|
+
if rep_path is None:
|
|
244
|
+
return self.__discovery_full
|
|
245
|
+
|
|
246
|
+
with open(rep_path, "r") as f:
|
|
247
|
+
lines_rep = f.readlines()
|
|
248
|
+
signals = list(filter(lambda x: x.startswith("SIGNAL") ,lines_rep))
|
|
249
|
+
signals = list(map(lambda x: x.split("\t")[1].strip() ,signals))
|
|
250
|
+
dicts_lines = list(filter(lambda x: x.startswith("DICTIONARY") ,lines_rep))
|
|
251
|
+
dicts_lines = list(map(lambda x: x.split("\t")[1].strip() ,dicts_lines))
|
|
252
|
+
if len(signals) ==0:
|
|
253
|
+
return self.__discovery_full
|
|
254
|
+
signals = signals[0]
|
|
255
|
+
if not os.path.isabs(signals):
|
|
256
|
+
signals = os.path.join(os.path.dirname(rep_path), signals)
|
|
257
|
+
with open(signals, "r") as f:
|
|
258
|
+
lines_signals = f.readlines()
|
|
259
|
+
sigs_in_signals = list(filter(lambda x: x.startswith("SIGNAL") ,lines_signals))
|
|
260
|
+
self.__discovery_full["signals"] = []
|
|
261
|
+
for sig in sigs_in_signals:
|
|
262
|
+
tokens = sig.split("\t")
|
|
263
|
+
sig_name = tokens[1].strip()
|
|
264
|
+
unit = "" if len(tokens) < 7 else tokens[6].strip()
|
|
265
|
+
sig_type = tokens[3].strip()
|
|
266
|
+
sig_categ = [] if len(tokens) < 6 else tokens[5].strip().split(",")
|
|
267
|
+
if sig_type == "0":
|
|
268
|
+
sig_type = "V(f)"
|
|
269
|
+
elif sig_type == "1":
|
|
270
|
+
sig_type = "T(i),V(f)"
|
|
271
|
+
elif sig_type == "2":
|
|
272
|
+
sig_type = "T(l),V(f),p,p,p,p"
|
|
273
|
+
elif sig_type == "3":
|
|
274
|
+
sig_type = "T(i,i),V(f)"
|
|
275
|
+
elif sig_type == "4":
|
|
276
|
+
sig_type = "T(l)"
|
|
277
|
+
elif sig_type == "5":
|
|
278
|
+
sig_type = "T(l,l),V(f),p,p,p,p"
|
|
279
|
+
elif sig_type == "6":
|
|
280
|
+
sig_type = "T(i),V(f,us),p,p"
|
|
281
|
+
elif sig_type == "7":
|
|
282
|
+
sig_type = "T(l),V(l)"
|
|
283
|
+
elif sig_type == "8":
|
|
284
|
+
sig_type = "T(i),V(s,s)"
|
|
285
|
+
elif sig_type == "9":
|
|
286
|
+
sig_type = "V(s,s)"
|
|
287
|
+
elif sig_type == "10":
|
|
288
|
+
sig_type = "V(s,s,s,s)"
|
|
289
|
+
elif sig_type == "11":
|
|
290
|
+
sig_type = "T(us),V(us)"
|
|
291
|
+
elif sig_type == "12":
|
|
292
|
+
sig_type = "T(i,i),V(f,f)"
|
|
293
|
+
elif sig_type == "13":
|
|
294
|
+
sig_type = "T(i),V(f,f)"
|
|
295
|
+
elif sig_type == "14":
|
|
296
|
+
sig_type = "T(l,l)"
|
|
297
|
+
elif sig_type == "15":
|
|
298
|
+
sig_type = "T(i),p,p,p,p,V(s,s,s,s)"
|
|
299
|
+
sig_block : dict[str, Any] = {
|
|
300
|
+
"code": sig_name,
|
|
301
|
+
"unit": unit,
|
|
302
|
+
"type": sig_type,
|
|
303
|
+
}
|
|
304
|
+
if len(sig_categ) > 0:
|
|
305
|
+
sig_block["categorical_channels"] = sig_categ
|
|
306
|
+
# Try to fetch category values and populate:
|
|
307
|
+
if len(list(filter(lambda x: x > "0", sig_categ))) > 0:
|
|
308
|
+
pass
|
|
309
|
+
for d in dicts_lines:
|
|
310
|
+
if not os.path.isabs(d):
|
|
311
|
+
d = os.path.join(os.path.dirname(rep_path), d)
|
|
312
|
+
with open(d, "r") as f:
|
|
313
|
+
lines_dict = f.readlines()
|
|
314
|
+
section = list(filter(lambda x: x.startswith("SECTION"),lines_dict))
|
|
315
|
+
if len(section) == 0:
|
|
316
|
+
continue
|
|
317
|
+
section = section[0]
|
|
318
|
+
all_sigs = section.split("\t")[1].strip().split(",")
|
|
319
|
+
if sig_name not in all_sigs:
|
|
320
|
+
continue
|
|
321
|
+
lines_dict = list(filter(lambda x: x.startswith("DEF"),lines_dict))
|
|
322
|
+
# Add this dict only if less than 10 categories:
|
|
323
|
+
full_dict = {}
|
|
324
|
+
for line in lines_dict:
|
|
325
|
+
tokens = line.split("\t")
|
|
326
|
+
id = tokens[1]
|
|
327
|
+
val = tokens[2].strip()
|
|
328
|
+
if id not in full_dict or len(val) > len(full_dict[id]):
|
|
329
|
+
full_dict[id] = val
|
|
330
|
+
if len(full_dict) < 10:
|
|
331
|
+
sig_block["categorical_values"] = []
|
|
332
|
+
for id, val in full_dict.items():
|
|
333
|
+
sig_block["categorical_values"].append(val)
|
|
334
|
+
else:
|
|
335
|
+
sig_block["categorical_values"] = []
|
|
336
|
+
|
|
337
|
+
#sig_block["categorical_values"]
|
|
338
|
+
|
|
339
|
+
self.__discovery_full["signals"].append(sig_block)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
return self.__discovery_full
|
|
344
|
+
|
|
345
|
+
def __load_algomarker(self, amconfig_path: str):
|
|
346
|
+
if not (os.path.exists(amconfig_path)):
|
|
347
|
+
raise NameError(
|
|
348
|
+
f'amconfig path "{amconfig_path}" not found. File Not Found'
|
|
349
|
+
)
|
|
350
|
+
assert self.__lib is not None
|
|
351
|
+
am_path = ctypes.create_string_buffer(amconfig_path.encode("ascii"))
|
|
352
|
+
res = self.__lib.AM_API_Load(self.__obj, am_path)
|
|
353
|
+
|
|
354
|
+
if res != 0:
|
|
355
|
+
raise NameError(f"Error in loading AlgoMarker: {res}")
|
|
356
|
+
else:
|
|
357
|
+
try:
|
|
358
|
+
info_js = self.discovery()
|
|
359
|
+
if "name" in info_js:
|
|
360
|
+
self.__name = info_js["name"]
|
|
361
|
+
print(f"Loaded {self.__name} AlgoMarker succefully")
|
|
362
|
+
except:
|
|
363
|
+
print("Warning: couldn't retrieve AlgoMarker Name")
|
|
364
|
+
|
|
365
|
+
def __repr__(self):
|
|
366
|
+
if self.__disposed:
|
|
367
|
+
return f"AlgoMarker was loaded with library {self.__libpath} and amconfig {self.__amconfig_path}, but disposed!"
|
|
368
|
+
if self.__name is not None:
|
|
369
|
+
return f"AlgoMarker {self.__name} was loaded with library {self.__libpath} and amconfig {self.__amconfig_path}"
|
|
370
|
+
else:
|
|
371
|
+
return f"AlgoMarker was loaded with library {self.__libpath} and amcofig {self.__amconfig_path}"
|
|
372
|
+
|
|
373
|
+
def dispose(self):
|
|
374
|
+
"""Disposes the AlgoMarker object and frees the memory"""
|
|
375
|
+
if self.__lib is not None:
|
|
376
|
+
self.__lib.AM_API_DisposeAlgoMarker(self.__obj)
|
|
377
|
+
self.__disposed = True
|
|
378
|
+
if self.__name is None:
|
|
379
|
+
print("Released AlgoMarker object")
|
|
380
|
+
else:
|
|
381
|
+
print(f'Released "{self.__name}" AlgoMarker object')
|
|
382
|
+
self.__lib = None
|
|
383
|
+
self.__obj = None
|
|
384
|
+
|
|
385
|
+
def __del__(self):
|
|
386
|
+
self.dispose()
|
|
387
|
+
|
|
388
|
+
def __enter__(self):
|
|
389
|
+
return self
|
|
390
|
+
|
|
391
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
392
|
+
self.dispose()
|
|
393
|
+
|
|
394
|
+
@__test_not_disposed
|
|
395
|
+
def __dispose_string_mem(self, obj):
|
|
396
|
+
assert self.__lib is not None
|
|
397
|
+
self.__lib.AM_API_Dispose(obj)
|
|
398
|
+
|
|
399
|
+
@__test_not_disposed
|
|
400
|
+
def get_name(self) -> dict[str, Any]:
|
|
401
|
+
"""Returns information about the Algomarkers in json format - input signals, name, version, etc."""
|
|
402
|
+
assert self.__lib is not None
|
|
403
|
+
res_name = ctypes.c_char_p()
|
|
404
|
+
self.__lib.AM_API_GetName(self.__obj, ctypes.byref(res_name))
|
|
405
|
+
try:
|
|
406
|
+
if res_name.value is None:
|
|
407
|
+
raise NameError("Error in getting AlgoMarker name - name is None")
|
|
408
|
+
res_discovery_str = res_name.value.decode("ascii")
|
|
409
|
+
# Clear memory:
|
|
410
|
+
res_discovery_str = {"name": res_discovery_str}
|
|
411
|
+
return res_discovery_str
|
|
412
|
+
except:
|
|
413
|
+
print("Error in discovery json conversion")
|
|
414
|
+
traceback.print_exc()
|
|
415
|
+
raise
|
|
416
|
+
|
|
417
|
+
@__test_not_disposed
|
|
418
|
+
def discovery(self) -> dict[str, Any]:
|
|
419
|
+
"""Returns information about the Algomarkers in json format - input signals, name, version, etc."""
|
|
420
|
+
assert self.__lib is not None
|
|
421
|
+
if self.api_version == 1:
|
|
422
|
+
js_res = self.__parse_config_amfile()
|
|
423
|
+
return js_res
|
|
424
|
+
res_discovery = ctypes.c_char_p()
|
|
425
|
+
self.__lib.AM_API_Discovery(self.__obj, ctypes.byref(res_discovery))
|
|
426
|
+
try:
|
|
427
|
+
res_discovery_str = res_discovery.value
|
|
428
|
+
# Clear memory:
|
|
429
|
+
self.__dispose_string_mem(res_discovery)
|
|
430
|
+
if res_discovery_str is None:
|
|
431
|
+
raise NameError(
|
|
432
|
+
"Error in getting AlgoMarker discovery - discovery is None"
|
|
433
|
+
)
|
|
434
|
+
res_discovery_str = json.loads(res_discovery_str)
|
|
435
|
+
return res_discovery_str
|
|
436
|
+
except:
|
|
437
|
+
print("Error in discovery json conversion")
|
|
438
|
+
traceback.print_exc()
|
|
439
|
+
raise
|
|
440
|
+
|
|
441
|
+
@__test_not_disposed
|
|
442
|
+
def clear_data(self):
|
|
443
|
+
"""Frees the algomarker patient data repository"""
|
|
444
|
+
assert self.__lib is not None
|
|
445
|
+
res = self.__lib.AM_API_ClearData(self.__obj)
|
|
446
|
+
if res != 0:
|
|
447
|
+
raise NameError(f"Error in clearing data - error code {res}")
|
|
448
|
+
|
|
449
|
+
@__test_not_disposed
|
|
450
|
+
def add_data_simple(
|
|
451
|
+
self, patient_id: int, signal_name: str, data: List[SingleDataElement]
|
|
452
|
+
) -> list[str]:
|
|
453
|
+
assert self.__lib is not None
|
|
454
|
+
flat_times = []
|
|
455
|
+
flat_values = []
|
|
456
|
+
times_size = None
|
|
457
|
+
values_size = None
|
|
458
|
+
messages = []
|
|
459
|
+
for elem in data:
|
|
460
|
+
flat_times.extend(elem.times)
|
|
461
|
+
flat_values.extend(elem.values)
|
|
462
|
+
if times_size is None:
|
|
463
|
+
times_size = len(elem.times)
|
|
464
|
+
if values_size is None:
|
|
465
|
+
values_size = len(elem.values)
|
|
466
|
+
if len(elem.times) != times_size:
|
|
467
|
+
raise ValueError(
|
|
468
|
+
f"Error in add_data_simple - all times must have the same size, but got {len(elem.times)} != {times_size}"
|
|
469
|
+
)
|
|
470
|
+
if len(elem.values) != values_size:
|
|
471
|
+
raise ValueError(
|
|
472
|
+
f"Error in add_data_simple - all values must have the same size, but got {len(elem.values)} != {values_size}"
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Convert to ctypes arrays
|
|
476
|
+
c_times = (ctypes.c_long * len(flat_times))(*flat_times)
|
|
477
|
+
c_values = (ctypes.c_float * len(flat_values))(*flat_values)
|
|
478
|
+
res = self.__lib.AM_API_AddData(
|
|
479
|
+
self.__obj,
|
|
480
|
+
patient_id,
|
|
481
|
+
ctypes.create_string_buffer(signal_name.encode("ascii")),
|
|
482
|
+
len(flat_times),
|
|
483
|
+
c_times,
|
|
484
|
+
len(flat_values),
|
|
485
|
+
c_values,
|
|
486
|
+
)
|
|
487
|
+
if res != 0:
|
|
488
|
+
msg = f"Error in add_data_simple - error code {res} for patient_id {patient_id}, signal_name {signal_name}, data: {elem}"
|
|
489
|
+
print(f"Error in add_data_simple - error code {res} for a patient more details in response message")
|
|
490
|
+
messages.append(msg)
|
|
491
|
+
# No return value, errors are handled by the library
|
|
492
|
+
return messages
|
|
493
|
+
|
|
494
|
+
@__test_not_disposed
|
|
495
|
+
def __add_data_old_api(self, json_data: str) -> list[str]:
|
|
496
|
+
"""This function recieves data json object and loads the data into the algomarker patient data repository.
|
|
497
|
+
Errors are collected in a string - each error in separate line. When there are no errors, the output is None.
|
|
498
|
+
|
|
499
|
+
Notes
|
|
500
|
+
-----
|
|
501
|
+
The input data json request is documented in different document and the potential errors
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
"""
|
|
506
|
+
js_req = json.loads(json_data) # Check if the json is valid
|
|
507
|
+
|
|
508
|
+
pid = int(js_req["patient_id"])
|
|
509
|
+
sigs_data = js_req["signals"]
|
|
510
|
+
all_data = []
|
|
511
|
+
messages = []
|
|
512
|
+
for sig_eme in sigs_data:
|
|
513
|
+
sig_name = sig_eme["code"]
|
|
514
|
+
data = sig_eme["data"]
|
|
515
|
+
all_data = []
|
|
516
|
+
for elem in data:
|
|
517
|
+
if "timestamp" not in elem or "value" not in elem:
|
|
518
|
+
raise ValueError(
|
|
519
|
+
f"Error in data json - each signal must have 'timestamp' and 'value' fields, but got {elem}"
|
|
520
|
+
)
|
|
521
|
+
timestamps = list(map(lambda x: int(x), elem["timestamp"]))
|
|
522
|
+
# AddDataStr for categorical signals is not supported right now. In current algomarkersm, there are not categorical signals
|
|
523
|
+
values = list(map(lambda x: float(x), elem["value"]))
|
|
524
|
+
sig_data = SingleDataElement(timestamps, values)
|
|
525
|
+
all_data.append(sig_data)
|
|
526
|
+
res = self.add_data_simple(pid, sig_name, all_data)
|
|
527
|
+
messages.extend(res)
|
|
528
|
+
return messages
|
|
529
|
+
|
|
530
|
+
@__test_not_disposed
|
|
531
|
+
def add_data(self, json_data: str) -> str | None:
|
|
532
|
+
"""This function recieves data json object and loads the data into the algomarker patient data repository.
|
|
533
|
+
Errors are collected in a string - each error in separate line. When there are no errors, the output is None.
|
|
534
|
+
|
|
535
|
+
Notes
|
|
536
|
+
-----
|
|
537
|
+
The input data json request is documented in different document and the potential errors
|
|
538
|
+
"""
|
|
539
|
+
assert self.__lib is not None
|
|
540
|
+
if self.api_version == 1:
|
|
541
|
+
res = self.__add_data_old_api(json_data)
|
|
542
|
+
if len(res) > 0:
|
|
543
|
+
return "\n".join(res)
|
|
544
|
+
else:
|
|
545
|
+
return None
|
|
546
|
+
# For new API
|
|
547
|
+
js_data = ctypes.create_string_buffer(json_data.encode("ascii"))
|
|
548
|
+
res_messages = ctypes.c_char_p()
|
|
549
|
+
res = self.__lib.AM_API_AddDataByType(
|
|
550
|
+
self.__obj, js_data, ctypes.byref(res_messages)
|
|
551
|
+
)
|
|
552
|
+
if res != 0:
|
|
553
|
+
print(f"AddData Failed {res}, messages ")
|
|
554
|
+
res_messages_str = res_messages.value
|
|
555
|
+
self.__dispose_string_mem(res_messages)
|
|
556
|
+
res_messages_str_val = ""
|
|
557
|
+
if res_messages_str is not None:
|
|
558
|
+
res_messages_str_val = res_messages_str.decode("ascii")
|
|
559
|
+
print(res_messages_str_val)
|
|
560
|
+
return res_messages_str_val
|
|
561
|
+
return None
|
|
562
|
+
|
|
563
|
+
@__test_not_disposed
|
|
564
|
+
def __calculate_old_api(self, request_json: str) -> dict[str, Any]:
|
|
565
|
+
"""Recieved json request for calculation and returns json string responde object with the result
|
|
566
|
+
|
|
567
|
+
Notes
|
|
568
|
+
-----
|
|
569
|
+
The input json request and json response results are documented in a different document
|
|
570
|
+
"""
|
|
571
|
+
assert self.__lib is not None
|
|
572
|
+
# 1. Create Request Object:
|
|
573
|
+
js_req = json.loads(request_json) # Check if the json is valid
|
|
574
|
+
assert (
|
|
575
|
+
js_req["type"] == "request"
|
|
576
|
+
) # "Error in request json - type must be 'request'"
|
|
577
|
+
request_type = ctypes.byref(ctypes.c_char_p(b"Raw")) # Default request type
|
|
578
|
+
requests = js_req["requests"]
|
|
579
|
+
load_data = js_req.get("load", 0)
|
|
580
|
+
pids = []
|
|
581
|
+
times = []
|
|
582
|
+
load_err_msgs = []
|
|
583
|
+
for req in requests:
|
|
584
|
+
if "patient_id" not in req or "time" not in req:
|
|
585
|
+
raise ValueError(
|
|
586
|
+
"Error in request json - each request must have patient_id and time"
|
|
587
|
+
)
|
|
588
|
+
patient_id = int(req["patient_id"])
|
|
589
|
+
time = int(req["time"])
|
|
590
|
+
pids.append(patient_id)
|
|
591
|
+
times.append(time)
|
|
592
|
+
if load_data:
|
|
593
|
+
if "data" not in req or "signals" not in req["data"]:
|
|
594
|
+
raise ValueError(
|
|
595
|
+
"Error in request json - when load is true, each request must have 'data' with 'signals'"
|
|
596
|
+
)
|
|
597
|
+
load_res = self.add_data(
|
|
598
|
+
json.dumps(
|
|
599
|
+
{"signals": req["data"]["signals"], "patient_id": patient_id}
|
|
600
|
+
)
|
|
601
|
+
)
|
|
602
|
+
if load_res is not None:
|
|
603
|
+
load_err_msgs.extend(load_res.split("\n"))
|
|
604
|
+
|
|
605
|
+
full_response = {
|
|
606
|
+
"type": "response",
|
|
607
|
+
"responses": [],
|
|
608
|
+
"request_id": js_req["request_id"],
|
|
609
|
+
}
|
|
610
|
+
if len(load_err_msgs) > 0:
|
|
611
|
+
full_response["errors"] = load_err_msgs
|
|
612
|
+
return full_response
|
|
613
|
+
# Convert to ctypes arrays
|
|
614
|
+
c_pids = (ctypes.c_int32 * len(pids))(*pids)
|
|
615
|
+
c_times = (ctypes.c_long * len(times))(*times)
|
|
616
|
+
req_object = ctypes.c_void_p()
|
|
617
|
+
self.__lib.AM_API_CreateRequest(
|
|
618
|
+
ctypes.create_string_buffer(js_req["request_id"].encode("ascii")),
|
|
619
|
+
request_type,
|
|
620
|
+
1,
|
|
621
|
+
c_pids,
|
|
622
|
+
c_times,
|
|
623
|
+
len(pids),
|
|
624
|
+
ctypes.byref(req_object),
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# 2. Create response object
|
|
628
|
+
response_object = ctypes.c_void_p()
|
|
629
|
+
self.__lib.AM_API_CreateResponses(ctypes.byref(response_object))
|
|
630
|
+
|
|
631
|
+
# 3. Call the Calculate function
|
|
632
|
+
# res_resp = ctypes.c_char_p()
|
|
633
|
+
res = self.__lib.AM_API_Calculate(self.__obj, req_object, response_object)
|
|
634
|
+
if res != 0:
|
|
635
|
+
print(f"Error in Calculate - error code {res}")
|
|
636
|
+
|
|
637
|
+
# 4. Check the result
|
|
638
|
+
n_resp = self.__lib.AM_API_GetResponsesNum(response_object)
|
|
639
|
+
print(f"Has {n_resp} responses")
|
|
640
|
+
# AM_API_GetSharedMessages(resp, &n_msgs, &msg_codes, &msgs_errs);
|
|
641
|
+
n_msgs = ctypes.c_int32()
|
|
642
|
+
msg_codes = ctypes.POINTER(ctypes.c_int32)()
|
|
643
|
+
msgs_errs = ctypes.POINTER(ctypes.c_char_p)()
|
|
644
|
+
res = self.__lib.AM_API_GetSharedMessages(
|
|
645
|
+
response_object,
|
|
646
|
+
ctypes.byref(n_msgs), # Number of messages
|
|
647
|
+
ctypes.byref(msg_codes), # Message codes
|
|
648
|
+
ctypes.byref(msgs_errs), # Messages errors
|
|
649
|
+
)
|
|
650
|
+
if res != 0:
|
|
651
|
+
print(f"Error in AM_API_GetSharedMessages - error code {res}")
|
|
652
|
+
full_response["errors"] = [
|
|
653
|
+
f"Error in AM_API_GetSharedMessages - error code {res}"
|
|
654
|
+
]
|
|
655
|
+
n_msgs = n_msgs.value
|
|
656
|
+
print(f"Response has {n_msgs} shared messages")
|
|
657
|
+
for i in range(n_msgs):
|
|
658
|
+
msg_code = msg_codes[i]
|
|
659
|
+
msg_err = msgs_errs[i].decode("ascii") if msgs_errs else "None"
|
|
660
|
+
if "errors" not in full_response:
|
|
661
|
+
full_response["errors"] = []
|
|
662
|
+
full_response["errors"].append(f"({msg_code}){msg_err}")
|
|
663
|
+
print(f"Message {i}: Code: {msg_code}, Error: {msg_err}")
|
|
664
|
+
|
|
665
|
+
for i in range(n_resp):
|
|
666
|
+
# AM_API_GetResponseAtIndex(response_object, i, &response);
|
|
667
|
+
# We would normally retrieve the response data here, but the old API does not provide a way to do this.
|
|
668
|
+
# Here we would normally retrieve the response data, but the old API does not provide a way to do this.
|
|
669
|
+
# We would need to implement the necessary functions in the C library to retrieve the response data.
|
|
670
|
+
# For example:
|
|
671
|
+
curr_resp_obj = ctypes.c_void_p()
|
|
672
|
+
curr_num = ctypes.c_int32()
|
|
673
|
+
# AM_API_GetResponseAtIndex(response_object, i, &response);
|
|
674
|
+
res = self.__lib.AM_API_GetResponseAtIndex(
|
|
675
|
+
response_object, i, ctypes.byref(curr_resp_obj)
|
|
676
|
+
)
|
|
677
|
+
if res != 0:
|
|
678
|
+
print(f"Error in fetch response {i} - error code {res}")
|
|
679
|
+
# AM_API_GetResponseScoresNum(response, &n_scores);
|
|
680
|
+
res = self.__lib.AM_API_GetResponseScoresNum(
|
|
681
|
+
curr_resp_obj, ctypes.byref(curr_num)
|
|
682
|
+
)
|
|
683
|
+
if res != 0:
|
|
684
|
+
print(f"Error in AM_API_GetResponseScoresNum {i} - error code {res}")
|
|
685
|
+
curr_num = curr_num.value
|
|
686
|
+
print(f"Has {curr_num} scores in response {i}")
|
|
687
|
+
# AM_API_GetResponsePoint(response, &pid, &ts);
|
|
688
|
+
pid = ctypes.c_int32()
|
|
689
|
+
ts = ctypes.c_long()
|
|
690
|
+
res = self.__lib.AM_API_GetResponsePoint(
|
|
691
|
+
curr_resp_obj, ctypes.byref(pid), ctypes.byref(ts)
|
|
692
|
+
)
|
|
693
|
+
if res != 0:
|
|
694
|
+
print(f"Error in AM_API_GetResponsePoint {i} - error code {res}")
|
|
695
|
+
pid = pid.value
|
|
696
|
+
ts = ts.value
|
|
697
|
+
print(f"Response {i} - Patient ID: {pid}, Timestamp: {ts}")
|
|
698
|
+
n_msgs = ctypes.c_int32()
|
|
699
|
+
msg_codes = ctypes.POINTER(ctypes.c_int32)()
|
|
700
|
+
msgs_errs = ctypes.POINTER(ctypes.c_char_p)()
|
|
701
|
+
# AM_API_GetResponseMessages(response, &n_msgs, &msg_codes, &msgs_errs);
|
|
702
|
+
res = self.__lib.AM_API_GetResponseMessages(
|
|
703
|
+
curr_resp_obj,
|
|
704
|
+
ctypes.byref(n_msgs), # Number of messages
|
|
705
|
+
ctypes.byref(msg_codes), # Message codes
|
|
706
|
+
ctypes.byref(msgs_errs), # Messages errors
|
|
707
|
+
)
|
|
708
|
+
if res != 0:
|
|
709
|
+
print(f"Error in AM_API_GetResponseMessages {i} - error code {res}")
|
|
710
|
+
n_msgs = n_msgs.value
|
|
711
|
+
js_resp = {
|
|
712
|
+
"patient_id": pid,
|
|
713
|
+
"time": ts,
|
|
714
|
+
"prediction": -9999,
|
|
715
|
+
"messages": [],
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
print(f"Response {i} has {n_msgs} messages")
|
|
719
|
+
for j in range(n_msgs):
|
|
720
|
+
msg_code = msg_codes[j]
|
|
721
|
+
msg_err = msgs_errs[j].decode("ascii") if msgs_errs else "None"
|
|
722
|
+
js_resp["messages"].append(f"({msg_code}){msg_err}")
|
|
723
|
+
print(f"Message {j}: Code: {msg_code}, Error: {msg_err}")
|
|
724
|
+
# AM_API_GetScoreMessages
|
|
725
|
+
for j in range(curr_num):
|
|
726
|
+
res = self.__lib.AM_API_GetScoreMessages(
|
|
727
|
+
curr_resp_obj,
|
|
728
|
+
j, # Assuming we want the first score messages
|
|
729
|
+
ctypes.byref(ctypes.c_int32(n_msgs)), # Number of messages
|
|
730
|
+
ctypes.byref(msg_codes), # Message codes
|
|
731
|
+
ctypes.byref(msgs_errs), # Messages errors
|
|
732
|
+
)
|
|
733
|
+
if res != 0:
|
|
734
|
+
print(
|
|
735
|
+
f"Error in AM_API_GetScoreMessages {i} {j} - error code {res}"
|
|
736
|
+
)
|
|
737
|
+
# resp_rc = AM_API_GetResponseScoreByIndex(response, 0, &_scr, &_scr_type);
|
|
738
|
+
scr_value: ctypes.c_float = ctypes.c_float()
|
|
739
|
+
scr_type: ctypes.c_char_p = ctypes.c_char_p()
|
|
740
|
+
for j in range(curr_num):
|
|
741
|
+
res = self.__lib.AM_API_GetResponseScoreByIndex(
|
|
742
|
+
curr_resp_obj, j, ctypes.byref(scr_value), ctypes.byref(scr_type)
|
|
743
|
+
)
|
|
744
|
+
if res != 0:
|
|
745
|
+
print(
|
|
746
|
+
f"Error in AM_API_GetResponseScoreByIndex {i} - error code {res}"
|
|
747
|
+
)
|
|
748
|
+
scr_value_v = scr_value.value
|
|
749
|
+
scr_type_v = None
|
|
750
|
+
if scr_type.value is not None:
|
|
751
|
+
scr_type_v = scr_type.value.decode("ascii") if scr_type else "None"
|
|
752
|
+
print(
|
|
753
|
+
f"Response {i} Score {j}: Value: {scr_value_v}, Type: {scr_type_v}"
|
|
754
|
+
)
|
|
755
|
+
# Take the right index from exports - currently only 'pred_0' is supported for sigle pred score
|
|
756
|
+
|
|
757
|
+
js_resp["prediction"] = scr_value_v
|
|
758
|
+
full_response["responses"].append(js_resp)
|
|
759
|
+
|
|
760
|
+
# 5. Dispose request and response objects
|
|
761
|
+
self.__lib.AM_API_DisposeRequest(req_object)
|
|
762
|
+
self.__lib.AM_API_DisposeResponses(response_object)
|
|
763
|
+
return full_response
|
|
764
|
+
|
|
765
|
+
@__test_not_disposed
|
|
766
|
+
def calculate(self, request_json: str) -> dict[str, Any]:
|
|
767
|
+
"""Recieved json request for calculation and returns json string responde object with the result
|
|
768
|
+
|
|
769
|
+
Notes
|
|
770
|
+
-----
|
|
771
|
+
The input json request and json response results are documented in a different document
|
|
772
|
+
"""
|
|
773
|
+
assert self.__lib is not None
|
|
774
|
+
if self.api_version == 1:
|
|
775
|
+
return self.__calculate_old_api(request_json)
|
|
776
|
+
js_req = ctypes.create_string_buffer(request_json.encode("ascii"))
|
|
777
|
+
res_resp = ctypes.c_char_p()
|
|
778
|
+
res = self.__lib.AM_API_CalculateByType(
|
|
779
|
+
self.__obj, 3001, js_req, ctypes.byref(res_resp)
|
|
780
|
+
)
|
|
781
|
+
if res != 0:
|
|
782
|
+
print(f"Calculate Failed {res}")
|
|
783
|
+
try:
|
|
784
|
+
res_resp_str = res_resp.value
|
|
785
|
+
self.__dispose_string_mem(res_resp)
|
|
786
|
+
if res_resp_str is None:
|
|
787
|
+
raise NameError("Error in Calculate - response is None")
|
|
788
|
+
res_resp_str = json.loads(res_resp_str)
|
|
789
|
+
return res_resp_str
|
|
790
|
+
except:
|
|
791
|
+
print("Error in converting respond json in calculate")
|
|
792
|
+
traceback.print_exc()
|
|
793
|
+
raise
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
# Old API testing
|
|
797
|
+
# bdate=(ctypes.c_long * 1)(*[1988])
|
|
798
|
+
# bdate_right=(ctypes.c_float * 1)(*[19880327])
|
|
799
|
+
# am.lib.AM_API_AddData(am.obj,1,ctypes.create_string_buffer(b"BDATE"),1, bdate,0 ,ctypes.POINTER(ctypes.c_float)())
|
|
800
|
+
# am.lib.AM_API_AddData(am.obj,1,ctypes.create_string_buffer(b"BDATE"),0, ctypes.POINTER(ctypes.c_long)(),1 ,bdate_right)
|
|
801
|
+
|
|
802
|
+
if __name__ == "__main__":
|
|
803
|
+
print(
|
|
804
|
+
"This is a module for AlgoMarker Python API. Use it as a module, not as a script."
|
|
805
|
+
)
|
|
806
|
+
print("Example usage:")
|
|
807
|
+
AlgoMarker_path = os.path.join(
|
|
808
|
+
os.environ["HOME"],
|
|
809
|
+
"Documents/MES/AlgoMarkers/AM_LGI/AlgoMarker/ColonFlag_3.1.0.0/ColonFlag-3.1.amconfig",
|
|
810
|
+
# "Documents/MES/AlgoMarkers/docker_images/LGI-Flag-ButWhy-3.1.2-Scorer/data/app/LGI-Flag-ButWhy-3.1.2-Scorer/LGI-ColonFlag-3.1.amconfig"
|
|
811
|
+
)
|
|
812
|
+
libpath = None
|
|
813
|
+
libpath = os.path.join(
|
|
814
|
+
os.environ["HOME"],
|
|
815
|
+
"Documents/MES/AlgoMarkers/AM_LGI/AlgoMarker/ColonFlag_3.1.0.0/libdyn_AlgoMarker.25102018_1.so",
|
|
816
|
+
# "Documents/MES/AlgoMarkers/docker_images/LGI-Flag-ButWhy-3.1.2-Scorer/data/app/LGI-Flag-ButWhy-3.1.2-Scorer/lib/libdyn_AlgoMarker.so"
|
|
817
|
+
)
|
|
818
|
+
request_json = AlgoMarker.create_request_json(1, 20240101)
|
|
819
|
+
with AlgoMarker(AlgoMarker_path, libpath) as am:
|
|
820
|
+
print(am.discovery())
|
|
821
|
+
am.clear_data()
|
|
822
|
+
am.add_data_simple(1, "BYEAR", [SingleDataElement([], [1978])])
|
|
823
|
+
am.add_data_simple(1, "GENDER", [SingleDataElement([], [1])])
|
|
824
|
+
am.add_data_simple(
|
|
825
|
+
1,
|
|
826
|
+
"Hemoglobin",
|
|
827
|
+
[
|
|
828
|
+
SingleDataElement([20220101], [14.5]),
|
|
829
|
+
SingleDataElement([20230101], [14.5]),
|
|
830
|
+
SingleDataElement([20240101], [14.5]),
|
|
831
|
+
],
|
|
832
|
+
)
|
|
833
|
+
am.add_data_simple(
|
|
834
|
+
1,
|
|
835
|
+
"Hematocrit",
|
|
836
|
+
[
|
|
837
|
+
SingleDataElement([20220101], [33]),
|
|
838
|
+
SingleDataElement([20230101], [33]),
|
|
839
|
+
SingleDataElement([20240101], [33]),
|
|
840
|
+
],
|
|
841
|
+
)
|
|
842
|
+
am.add_data_simple(
|
|
843
|
+
1,
|
|
844
|
+
"MCH",
|
|
845
|
+
[
|
|
846
|
+
SingleDataElement([20220101], [33]),
|
|
847
|
+
SingleDataElement([20230101], [33]),
|
|
848
|
+
SingleDataElement([20240101], [33]),
|
|
849
|
+
],
|
|
850
|
+
)
|
|
851
|
+
am.add_data_simple(
|
|
852
|
+
1,
|
|
853
|
+
"RBC",
|
|
854
|
+
[
|
|
855
|
+
SingleDataElement([20220101], [4.5]),
|
|
856
|
+
SingleDataElement([20230101], [4.5]),
|
|
857
|
+
SingleDataElement([20240101], [4.5]),
|
|
858
|
+
],
|
|
859
|
+
)
|
|
860
|
+
am.add_data_simple(
|
|
861
|
+
1,
|
|
862
|
+
"MCV",
|
|
863
|
+
[
|
|
864
|
+
SingleDataElement([20220101], [90]),
|
|
865
|
+
SingleDataElement([20230101], [90]),
|
|
866
|
+
SingleDataElement([20240101], [90]),
|
|
867
|
+
],
|
|
868
|
+
)
|
|
869
|
+
resp = am.calculate(request_json)
|
|
870
|
+
print("Response:")
|
|
871
|
+
print(resp)
|
|
872
|
+
print("Done with AlgoMarker example")
|