medpython 1.1.1__cp310-cp310-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.
@@ -0,0 +1,749 @@
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
+
223
+ def __load_algomarker(self, amconfig_path: str):
224
+ if not (os.path.exists(amconfig_path)):
225
+ raise NameError(
226
+ f'amconfig path "{amconfig_path}" not found. File Not Found'
227
+ )
228
+ assert self.__lib is not None
229
+ am_path = ctypes.create_string_buffer(amconfig_path.encode("ascii"))
230
+ res = self.__lib.AM_API_Load(self.__obj, am_path)
231
+
232
+ if res != 0:
233
+ raise NameError(f"Error in loading AlgoMarker: {res}")
234
+ else:
235
+ try:
236
+ info_js = self.discovery()
237
+ if "name" in info_js:
238
+ self.__name = info_js["name"]
239
+ print(f"Loaded {self.__name} AlgoMarker succefully")
240
+ except:
241
+ print("Warning: couldn't retrieve AlgoMarker Name")
242
+
243
+ def __repr__(self):
244
+ if self.__disposed:
245
+ return f"AlgoMarker was loaded with library {self.__libpath} and amconfig {self.__amconfig_path}, but disposed!"
246
+ if self.__name is not None:
247
+ return f"AlgoMarker {self.__name} was loaded with library {self.__libpath} and amconfig {self.__amconfig_path}"
248
+ else:
249
+ return f"AlgoMarker was loaded with library {self.__libpath} and amcofig {self.__amconfig_path}"
250
+
251
+ def dispose(self):
252
+ """Disposes the AlgoMarker object and frees the memory"""
253
+ if self.__lib is not None:
254
+ self.__lib.AM_API_DisposeAlgoMarker(self.__obj)
255
+ self.__disposed = True
256
+ if self.__name is None:
257
+ print("Released AlgoMarker object")
258
+ else:
259
+ print(f'Released "{self.__name}" AlgoMarker object')
260
+ self.__lib = None
261
+ self.__obj = None
262
+
263
+ def __del__(self):
264
+ self.dispose()
265
+
266
+ def __enter__(self):
267
+ return self
268
+
269
+ def __exit__(self, exc_type, exc_value, exc_traceback):
270
+ self.dispose()
271
+
272
+ @__test_not_disposed
273
+ def __dispose_string_mem(self, obj):
274
+ assert self.__lib is not None
275
+ self.__lib.AM_API_Dispose(obj)
276
+
277
+ @__test_not_disposed
278
+ def get_name(self) -> dict[str, Any]:
279
+ """Returns information about the Algomarkers in json format - input signals, name, version, etc."""
280
+ assert self.__lib is not None
281
+ res_name = ctypes.c_char_p()
282
+ self.__lib.AM_API_GetName(self.__obj, ctypes.byref(res_name))
283
+ try:
284
+ if res_name.value is None:
285
+ raise NameError("Error in getting AlgoMarker name - name is None")
286
+ res_discovery_str = res_name.value.decode("ascii")
287
+ # Clear memory:
288
+ res_discovery_str = {"name": res_discovery_str}
289
+ return res_discovery_str
290
+ except:
291
+ print("Error in discovery json conversion")
292
+ traceback.print_exc()
293
+ raise
294
+
295
+ @__test_not_disposed
296
+ def discovery(self) -> dict[str, Any]:
297
+ """Returns information about the Algomarkers in json format - input signals, name, version, etc."""
298
+ assert self.__lib is not None
299
+ if self.api_version == 1:
300
+ return self.get_name()
301
+ res_discovery = ctypes.c_char_p()
302
+ self.__lib.AM_API_Discovery(self.__obj, ctypes.byref(res_discovery))
303
+ try:
304
+ res_discovery_str = res_discovery.value
305
+ # Clear memory:
306
+ self.__dispose_string_mem(res_discovery)
307
+ if res_discovery_str is None:
308
+ raise NameError(
309
+ "Error in getting AlgoMarker discovery - discovery is None"
310
+ )
311
+ res_discovery_str = json.loads(res_discovery_str)
312
+ return res_discovery_str
313
+ except:
314
+ print("Error in discovery json conversion")
315
+ traceback.print_exc()
316
+ raise
317
+
318
+ @__test_not_disposed
319
+ def clear_data(self):
320
+ """Frees the algomarker patient data repository"""
321
+ assert self.__lib is not None
322
+ res = self.__lib.AM_API_ClearData(self.__obj)
323
+ if res != 0:
324
+ raise NameError(f"Error in clearing data - error code {res}")
325
+
326
+ @__test_not_disposed
327
+ def add_data_simple(
328
+ self, patient_id: int, signal_name: str, data: List[SingleDataElement]
329
+ ) -> list[str]:
330
+ assert self.__lib is not None
331
+ flat_times = []
332
+ flat_values = []
333
+ times_size = None
334
+ values_size = None
335
+ messages = []
336
+ for elem in data:
337
+ flat_times.extend(elem.times)
338
+ flat_values.extend(elem.values)
339
+ if times_size is None:
340
+ times_size = len(elem.times)
341
+ if values_size is None:
342
+ values_size = len(elem.values)
343
+ if len(elem.times) != times_size:
344
+ raise ValueError(
345
+ f"Error in add_data_simple - all times must have the same size, but got {len(elem.times)} != {times_size}"
346
+ )
347
+ if len(elem.values) != values_size:
348
+ raise ValueError(
349
+ f"Error in add_data_simple - all values must have the same size, but got {len(elem.values)} != {values_size}"
350
+ )
351
+
352
+ # Convert to ctypes arrays
353
+ c_times = (ctypes.c_long * len(flat_times))(*flat_times)
354
+ c_values = (ctypes.c_float * len(flat_values))(*flat_values)
355
+ res = self.__lib.AM_API_AddData(
356
+ self.__obj,
357
+ patient_id,
358
+ ctypes.create_string_buffer(signal_name.encode("ascii")),
359
+ len(flat_times),
360
+ c_times,
361
+ len(flat_values),
362
+ c_values,
363
+ )
364
+ if res != 0:
365
+ msg = f"Error in add_data_simple - error code {res} for patient_id {patient_id}, signal_name {signal_name}, data: {elem}"
366
+ print(f"Error in add_data_simple - error code {res} for a patient more details in response message")
367
+ messages.append(msg)
368
+ # No return value, errors are handled by the library
369
+ return messages
370
+
371
+ @__test_not_disposed
372
+ def __add_data_old_api(self, json_data: str) -> list[str]:
373
+ """This function recieves data json object and loads the data into the algomarker patient data repository.
374
+ Errors are collected in a string - each error in separate line. When there are no errors, the output is None.
375
+
376
+ Notes
377
+ -----
378
+ The input data json request is documented in different document and the potential errors
379
+ """
380
+
381
+ """
382
+ """
383
+ js_req = json.loads(json_data) # Check if the json is valid
384
+
385
+ pid = int(js_req["patient_id"])
386
+ sigs_data = js_req["signals"]
387
+ all_data = []
388
+ messages = []
389
+ for sig_eme in sigs_data:
390
+ sig_name = sig_eme["code"]
391
+ data = sig_eme["data"]
392
+ all_data = []
393
+ for elem in data:
394
+ if "timestamp" not in elem or "value" not in elem:
395
+ raise ValueError(
396
+ f"Error in data json - each signal must have 'timestamp' and 'value' fields, but got {elem}"
397
+ )
398
+ timestamps = list(map(lambda x: int(x), elem["timestamp"]))
399
+ # AddDataStr for categorical signals is not supported right now. In current algomarkersm, there are not categorical signals
400
+ values = list(map(lambda x: float(x), elem["value"]))
401
+ sig_data = SingleDataElement(timestamps, values)
402
+ all_data.append(sig_data)
403
+ res = self.add_data_simple(pid, sig_name, all_data)
404
+ messages.extend(res)
405
+ return messages
406
+
407
+ @__test_not_disposed
408
+ def add_data(self, json_data: str) -> str | None:
409
+ """This function recieves data json object and loads the data into the algomarker patient data repository.
410
+ Errors are collected in a string - each error in separate line. When there are no errors, the output is None.
411
+
412
+ Notes
413
+ -----
414
+ The input data json request is documented in different document and the potential errors
415
+ """
416
+ assert self.__lib is not None
417
+ if self.api_version == 1:
418
+ res = self.__add_data_old_api(json_data)
419
+ if len(res)> 0:
420
+ return "\n".join(res)
421
+ else:
422
+ return None
423
+ # For new API
424
+ js_data = ctypes.create_string_buffer(json_data.encode("ascii"))
425
+ res_messages = ctypes.c_char_p()
426
+ res = self.__lib.AM_API_AddDataByType(
427
+ self.__obj, js_data, ctypes.byref(res_messages)
428
+ )
429
+ if res != 0:
430
+ print(f"AddData Failed {res}, messages ")
431
+ res_messages_str = res_messages.value
432
+ self.__dispose_string_mem(res_messages)
433
+ res_messages_str_val = ""
434
+ if res_messages_str is not None:
435
+ res_messages_str_val = res_messages_str.decode("ascii")
436
+ print(res_messages_str_val)
437
+ return res_messages_str_val
438
+ return None
439
+
440
+ @__test_not_disposed
441
+ def __calculate_old_api(self, request_json: str) -> dict[str, Any]:
442
+ """Recieved json request for calculation and returns json string responde object with the result
443
+
444
+ Notes
445
+ -----
446
+ The input json request and json response results are documented in a different document
447
+ """
448
+ assert self.__lib is not None
449
+ # 1. Create Request Object:
450
+ js_req = json.loads(request_json) # Check if the json is valid
451
+ assert (
452
+ js_req["type"] == "request"
453
+ ) # "Error in request json - type must be 'request'"
454
+ request_type = ctypes.byref(ctypes.c_char_p(b"Raw")) # Default request type
455
+ requests = js_req["requests"]
456
+ load_data = js_req.get("load", 0)
457
+ pids = []
458
+ times = []
459
+ load_err_msgs= []
460
+ for req in requests:
461
+ if "patient_id" not in req or "time" not in req:
462
+ raise ValueError(
463
+ "Error in request json - each request must have patient_id and time"
464
+ )
465
+ patient_id = int(req["patient_id"])
466
+ time = int(req["time"])
467
+ pids.append(patient_id)
468
+ times.append(time)
469
+ if load_data:
470
+ if "data" not in req or "signals" not in req["data"]:
471
+ raise ValueError(
472
+ "Error in request json - when load is true, each request must have 'data' with 'signals'"
473
+ )
474
+ load_res = self.add_data(
475
+ json.dumps(
476
+ {"signals": req["data"]["signals"], "patient_id": patient_id}
477
+ )
478
+ )
479
+ if load_res is not None:
480
+ load_err_msgs.extend(load_res.split("\n"))
481
+
482
+ full_response = {
483
+ "type": "response",
484
+ "responses": [],
485
+ "request_id": js_req["request_id"],
486
+ }
487
+ if len(load_err_msgs) > 0:
488
+ full_response["errors"] = load_err_msgs
489
+ return full_response
490
+ # Convert to ctypes arrays
491
+ c_pids = (ctypes.c_int32 * len(pids))(*pids)
492
+ c_times = (ctypes.c_long * len(times))(*times)
493
+ req_object = ctypes.c_void_p()
494
+ self.__lib.AM_API_CreateRequest(
495
+ ctypes.create_string_buffer(js_req["request_id"].encode("ascii")),
496
+ request_type,
497
+ 1,
498
+ c_pids,
499
+ c_times,
500
+ len(pids),
501
+ ctypes.byref(req_object),
502
+ )
503
+
504
+ # 2. Create response object
505
+ response_object = ctypes.c_void_p()
506
+ self.__lib.AM_API_CreateResponses(ctypes.byref(response_object))
507
+
508
+ # 3. Call the Calculate function
509
+ # res_resp = ctypes.c_char_p()
510
+ res = self.__lib.AM_API_Calculate(self.__obj, req_object, response_object)
511
+ if res != 0:
512
+ print(f"Error in Calculate - error code {res}")
513
+
514
+ # 4. Check the result
515
+ n_resp = self.__lib.AM_API_GetResponsesNum(response_object)
516
+ print(f"Has {n_resp} responses")
517
+ # AM_API_GetSharedMessages(resp, &n_msgs, &msg_codes, &msgs_errs);
518
+ n_msgs = ctypes.c_int32()
519
+ msg_codes = ctypes.POINTER(ctypes.c_int32)()
520
+ msgs_errs = ctypes.POINTER(ctypes.c_char_p)()
521
+ res = self.__lib.AM_API_GetSharedMessages(
522
+ response_object,
523
+ ctypes.byref(n_msgs), # Number of messages
524
+ ctypes.byref(msg_codes), # Message codes
525
+ ctypes.byref(msgs_errs), # Messages errors
526
+ )
527
+ if res != 0:
528
+ print(f"Error in AM_API_GetSharedMessages - error code {res}")
529
+ full_response["errors"] = [
530
+ f"Error in AM_API_GetSharedMessages - error code {res}"
531
+ ]
532
+ n_msgs = n_msgs.value
533
+ print(f"Response has {n_msgs} shared messages")
534
+ for i in range(n_msgs):
535
+ msg_code = msg_codes[i]
536
+ msg_err = msgs_errs[i].decode("ascii") if msgs_errs else "None"
537
+ if "errors" not in full_response:
538
+ full_response["errors"] = []
539
+ full_response["errors"].append(f"({msg_code}){msg_err}")
540
+ print(f"Message {i}: Code: {msg_code}, Error: {msg_err}")
541
+
542
+ for i in range(n_resp):
543
+ # AM_API_GetResponseAtIndex(response_object, i, &response);
544
+ # We would normally retrieve the response data here, but the old API does not provide a way to do this.
545
+ # Here we would normally retrieve the response data, but the old API does not provide a way to do this.
546
+ # We would need to implement the necessary functions in the C library to retrieve the response data.
547
+ # For example:
548
+ curr_resp_obj = ctypes.c_void_p()
549
+ curr_num = ctypes.c_int32()
550
+ # AM_API_GetResponseAtIndex(response_object, i, &response);
551
+ res = self.__lib.AM_API_GetResponseAtIndex(
552
+ response_object, i, ctypes.byref(curr_resp_obj)
553
+ )
554
+ if res != 0:
555
+ print(f"Error in fetch response {i} - error code {res}")
556
+ # AM_API_GetResponseScoresNum(response, &n_scores);
557
+ res = self.__lib.AM_API_GetResponseScoresNum(
558
+ curr_resp_obj, ctypes.byref(curr_num)
559
+ )
560
+ if res != 0:
561
+ print(f"Error in AM_API_GetResponseScoresNum {i} - error code {res}")
562
+ curr_num = curr_num.value
563
+ print(f"Has {curr_num} scores in response {i}")
564
+ # AM_API_GetResponsePoint(response, &pid, &ts);
565
+ pid = ctypes.c_int32()
566
+ ts = ctypes.c_long()
567
+ res = self.__lib.AM_API_GetResponsePoint(
568
+ curr_resp_obj, ctypes.byref(pid), ctypes.byref(ts)
569
+ )
570
+ if res != 0:
571
+ print(f"Error in AM_API_GetResponsePoint {i} - error code {res}")
572
+ pid = pid.value
573
+ ts = ts.value
574
+ print(f"Response {i} - Patient ID: {pid}, Timestamp: {ts}")
575
+ n_msgs = ctypes.c_int32()
576
+ msg_codes = ctypes.POINTER(ctypes.c_int32)()
577
+ msgs_errs = ctypes.POINTER(ctypes.c_char_p)()
578
+ # AM_API_GetResponseMessages(response, &n_msgs, &msg_codes, &msgs_errs);
579
+ res = self.__lib.AM_API_GetResponseMessages(
580
+ curr_resp_obj,
581
+ ctypes.byref(n_msgs), # Number of messages
582
+ ctypes.byref(msg_codes), # Message codes
583
+ ctypes.byref(msgs_errs), # Messages errors
584
+ )
585
+ if res != 0:
586
+ print(f"Error in AM_API_GetResponseMessages {i} - error code {res}")
587
+ n_msgs = n_msgs.value
588
+ js_resp = {
589
+ "patient_id": pid,
590
+ "time": ts,
591
+ "prediction": -9999,
592
+ "messages": [],
593
+ }
594
+
595
+ print(f"Response {i} has {n_msgs} messages")
596
+ for j in range(n_msgs):
597
+ msg_code = msg_codes[j]
598
+ msg_err = msgs_errs[j].decode("ascii") if msgs_errs else "None"
599
+ js_resp["messages"].append(f"({msg_code}){msg_err}")
600
+ print(f"Message {j}: Code: {msg_code}, Error: {msg_err}")
601
+ # AM_API_GetScoreMessages
602
+ for j in range(curr_num):
603
+ res = self.__lib.AM_API_GetScoreMessages(
604
+ curr_resp_obj,
605
+ j, # Assuming we want the first score messages
606
+ ctypes.byref(ctypes.c_int32(n_msgs)), # Number of messages
607
+ ctypes.byref(msg_codes), # Message codes
608
+ ctypes.byref(msgs_errs), # Messages errors
609
+ )
610
+ if res != 0:
611
+ print(
612
+ f"Error in AM_API_GetScoreMessages {i} {j} - error code {res}"
613
+ )
614
+ # resp_rc = AM_API_GetResponseScoreByIndex(response, 0, &_scr, &_scr_type);
615
+ scr_value: ctypes.c_float = ctypes.c_float()
616
+ scr_type: ctypes.c_char_p = ctypes.c_char_p()
617
+ for j in range(curr_num):
618
+ res = self.__lib.AM_API_GetResponseScoreByIndex(
619
+ curr_resp_obj, j, ctypes.byref(scr_value), ctypes.byref(scr_type)
620
+ )
621
+ if res != 0:
622
+ print(
623
+ f"Error in AM_API_GetResponseScoreByIndex {i} - error code {res}"
624
+ )
625
+ scr_value_v = scr_value.value
626
+ scr_type_v = None
627
+ if scr_type.value is not None:
628
+ scr_type_v = scr_type.value.decode("ascii") if scr_type else "None"
629
+ print(
630
+ f"Response {i} Score {j}: Value: {scr_value_v}, Type: {scr_type_v}"
631
+ )
632
+ # Take the right index from exports - currently only 'pred_0' is supported for sigle pred score
633
+
634
+ js_resp["prediction"] = scr_value_v
635
+ full_response["responses"].append(js_resp)
636
+
637
+ # 5. Dispose request and response objects
638
+ self.__lib.AM_API_DisposeRequest(req_object)
639
+ self.__lib.AM_API_DisposeResponses(response_object)
640
+ return full_response
641
+
642
+ @__test_not_disposed
643
+ def calculate(self, request_json: str) -> dict[str, Any]:
644
+ """Recieved json request for calculation and returns json string responde object with the result
645
+
646
+ Notes
647
+ -----
648
+ The input json request and json response results are documented in a different document
649
+ """
650
+ assert self.__lib is not None
651
+ if self.api_version == 1:
652
+ return self.__calculate_old_api(request_json)
653
+ js_req = ctypes.create_string_buffer(request_json.encode("ascii"))
654
+ res_resp = ctypes.c_char_p()
655
+ res = self.__lib.AM_API_CalculateByType(
656
+ self.__obj, 3001, js_req, ctypes.byref(res_resp)
657
+ )
658
+ if res != 0:
659
+ print(f"Calculate Failed {res}")
660
+ try:
661
+ res_resp_str = res_resp.value
662
+ self.__dispose_string_mem(res_resp)
663
+ if res_resp_str is None:
664
+ raise NameError("Error in Calculate - response is None")
665
+ res_resp_str = json.loads(res_resp_str)
666
+ return res_resp_str
667
+ except:
668
+ print("Error in converting respond json in calculate")
669
+ traceback.print_exc()
670
+ raise
671
+
672
+
673
+ # Old API testing
674
+ # bdate=(ctypes.c_long * 1)(*[1988])
675
+ # bdate_right=(ctypes.c_float * 1)(*[19880327])
676
+ # am.lib.AM_API_AddData(am.obj,1,ctypes.create_string_buffer(b"BDATE"),1, bdate,0 ,ctypes.POINTER(ctypes.c_float)())
677
+ # am.lib.AM_API_AddData(am.obj,1,ctypes.create_string_buffer(b"BDATE"),0, ctypes.POINTER(ctypes.c_long)(),1 ,bdate_right)
678
+
679
+ if __name__ == "__main__":
680
+ print(
681
+ "This is a module for AlgoMarker Python API. Use it as a module, not as a script."
682
+ )
683
+ print("Example usage:")
684
+ AlgoMarker_path = os.path.join(
685
+ os.environ["HOME"],
686
+ "Documents/MES/AlgoMarkers/AM_LGI/AlgoMarker/ColonFlag_3.1.0.0/ColonFlag-3.1.amconfig",
687
+ # "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"
688
+ )
689
+ libpath = None
690
+ libpath = os.path.join(
691
+ os.environ["HOME"],
692
+ "Documents/MES/AlgoMarkers/AM_LGI/AlgoMarker/ColonFlag_3.1.0.0/libdyn_AlgoMarker.25102018_1.so",
693
+ # "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"
694
+ )
695
+ request_json = AlgoMarker.create_request_json(1, 20240101)
696
+ with AlgoMarker(AlgoMarker_path, libpath) as am:
697
+ print(am.discovery())
698
+ am.clear_data()
699
+ am.add_data_simple(1, "BYEAR", [SingleDataElement([], [1978])])
700
+ am.add_data_simple(1, "GENDER", [SingleDataElement([], [1])])
701
+ am.add_data_simple(
702
+ 1,
703
+ "Hemoglobin",
704
+ [
705
+ SingleDataElement([20220101], [14.5]),
706
+ SingleDataElement([20230101], [14.5]),
707
+ SingleDataElement([20240101], [14.5]),
708
+ ],
709
+ )
710
+ am.add_data_simple(
711
+ 1,
712
+ "Hematocrit",
713
+ [
714
+ SingleDataElement([20220101], [33]),
715
+ SingleDataElement([20230101], [33]),
716
+ SingleDataElement([20240101], [33]),
717
+ ],
718
+ )
719
+ am.add_data_simple(
720
+ 1,
721
+ "MCH",
722
+ [
723
+ SingleDataElement([20220101], [33]),
724
+ SingleDataElement([20230101], [33]),
725
+ SingleDataElement([20240101], [33]),
726
+ ],
727
+ )
728
+ am.add_data_simple(
729
+ 1,
730
+ "RBC",
731
+ [
732
+ SingleDataElement([20220101], [4.5]),
733
+ SingleDataElement([20230101], [4.5]),
734
+ SingleDataElement([20240101], [4.5]),
735
+ ],
736
+ )
737
+ am.add_data_simple(
738
+ 1,
739
+ "MCV",
740
+ [
741
+ SingleDataElement([20220101], [90]),
742
+ SingleDataElement([20230101], [90]),
743
+ SingleDataElement([20240101], [90]),
744
+ ],
745
+ )
746
+ resp = am.calculate(request_json)
747
+ print("Response:")
748
+ print(resp)
749
+ print("Done with AlgoMarker example")