HABSlib: Python Library for EEG Analysis and Biomarker Evaluation
HABSlib is a versatile Python library designed to facilitate interaction with the HABS BrainOS API for comprehensive EEG data analysis and biomarker evaluation. Developed to support neuroscientific research and clinical applications, HABSlib simplifies the process of fetching, processing, and analyzing EEG data through a user-friendly interface.
Key Features:
- API Integration: Connects with the BrainOS HABS API, allowing users to access EEG data and related services effortlessly.
- Data Management: Provides a robust interface for managing EEG datasets, including storage on the HABS servers.
- Biomarker Evaluation: Enables the analysis of EEG biomarkers, essential for diagnosing and monitoring neurological conditions.
- Customizable Pipelines: Users can create custom analysis pipelines tailored to specific research needs, ensuring flexibility and adaptability in various use cases.
Sessions
The communications between the user and HABS BrainOS is based on a RESTful API (see doc) and structured into sessions.
A Session with the HABS BrainOS iinitiates with an handshake during which encryption keys are exchanged for the security of any following communication between the user and the server.
Simple sessions
There are two general types of session: real-time and off-line.
In setting a either a real-time or off-line session, the user provides the session metadata, such as their user_id, session_date, session type (all required), and additional notes depending on the nature of recordings.
Then, in a real-time session, the user specifies the type of EEG DEVICE ('board') used, the duration of the EEG recording, and the frequency of server update.
In an off-line session, the user specifies a session id (referring to data already exisiting, either acquired live at some point in time, or from an uploaded file).
The HABSlib can read EDF files (EDF file type only, for now, but it's growing) and sends it to the server.
In these simple types of session, after the real-time or offline uploading, the data can be selected via the session_id for further processing.
Piped sessions
There is another type of session, called piped session. This type of session is meant to help you organize the flow of analysis and make it reproducible.
Usually, an analysis implies several steps over the raw data. BrainOS allows you to perform a growing number of predefined and parametrizable functions over the data, to filter, remove artifacts, and extract features.
And you can do it without taking the output of one function and passing it to another. You can pipe (|) the output of one function into the next.
This session type also is available as real-time and off-line. In the real-time version the EEG data is processed as per pipe by the server as soon as it is received, and the results are sent back to the user as soon as they are processed.
1###################################################### 2# INTRO 3 4r""" 5# HABSlib: Python Library for EEG Analysis and Biomarker Evaluation 6 7HABSlib is a versatile Python library designed to facilitate interaction with the HABS BrainOS API for comprehensive EEG data analysis and biomarker evaluation. 8Developed to support neuroscientific research and clinical applications, HABSlib simplifies the process of fetching, processing, and analyzing EEG data through a user-friendly interface. 9 10Key Features: 11- **API Integration**: Connects with the BrainOS HABS API, allowing users to access EEG data and related services effortlessly. 12- **Data Management**: Provides a robust interface for managing EEG datasets, including storage on the HABS servers. 13- **Biomarker Evaluation**: Enables the analysis of EEG biomarkers, essential for diagnosing and monitoring neurological conditions. 14- **Customizable Pipelines**: Users can create custom analysis pipelines tailored to specific research needs, ensuring flexibility and adaptability in various use cases. 15 16## Sessions 17 18The communications between the user and HABS BrainOS is based on a RESTful API (see doc) and structured into sessions. 19 20A *Session* with the HABS BrainOS iinitiates with an handshake during which encryption keys are exchanged for the security of any following communication between the user and the server. 21 22### Simple sessions 23 24There are two general types of session: *real-time* and *off-line*. 25 26In setting a either a real-time or off-line session, the user provides the session metadata, such as their user_id, session_date, session type (all required), and additional notes depending on the nature of recordings. 27 28Then, in a real-time session, the user specifies the type of EEG DEVICE ('board') used, the duration of the EEG recording, and the frequency of server update. 29 30In an off-line session, the user specifies a session id (referring to data already exisiting, either acquired live at some point in time, or from an uploaded file). 31The HABSlib can read EDF files (EDF file type only, for now, but it's growing) and sends it to the server. 32 33In these simple types of session, after the real-time or offline uploading, the data can be selected via the session_id for further processing. 34 35### Piped sessions 36 37There is another type of session, called *piped* session. This type of session is meant to help you organize the flow of analysis and make it reproducible. 38Usually, an analysis implies several steps over the raw data. BrainOS allows you to perform a growing number of predefined and parametrizable functions over the data, to filter, remove artifacts, and extract features. 39And you can do it without taking the output of one function and passing it to another. You can pipe (|) the output of one function into the next. 40 41This session type also is available as *real-time* and *off-line*. In the *real-time* version the EEG data is processed as per pipe by the server as soon as it is received, and the results are sent back to the user as soon as they are processed. 42""" 43 44import sys 45import os 46import base64 47import requests 48import json 49import jsonschema 50from jsonschema import validate 51from jsonschema import exceptions 52# from bson import json_util 53 54import numpy as np 55 56import time 57from datetime import datetime 58import uuid 59import asyncio 60import webbrowser 61 62from . import BASE_URL, VERSION 63from . import BoardManager 64 65from cryptography.hazmat.primitives import hashes, serialization 66from cryptography.hazmat.backends import default_backend 67from . import store_public_key, load_public_key, generate_aes_key, encrypt_aes_key_with_rsa, encrypt_message, decrypt_message 68 69from pyedflib import highlevel 70 71from importlib.metadata import version 72 73 74###################################################### 75# validate the metadata against a specified schema 76def validate_metadata(metadata, schema_name, schemafile='metadata.json'): 77 """ 78 Validate metadata against a given JSON schema. 79 80 Args: 81 **metadata** (*dict*): The metadata to be validated. 82 **schema_name** (*str*): The name of the schema to validate against. HABSlib currently supports the validation of Session metadata and User data. 83 **schemafile** (*str*, optional): The path to the JSON file containing the schemas. Defaults to 'metadata.json'. 84 85 Returns: 86 *bool*: True if validation is successful, False otherwise. 87 88 Raises: 89 **FileNotFoundError**: If the schema file does not exist. 90 **json.JSONDecodeError**: If there is an error decoding the JSON schema file. 91 **exceptions.ValidationError**: If the metadata does not conform to the schema. 92 **Exception**: For any other errors that occur during validation. 93 94 Example: 95 ``` 96 metadata = {"name": "example", "type": "data"} 97 schema_name = "example_schema" 98 is_valid = validate_metadata(metadata, schema_name) 99 if is_valid: 100 print("Metadata is valid.") 101 else: 102 print("Metadata is invalid.") 103 ``` 104 """ 105 print(metadata) 106 try: 107 with open(os.path.join(os.path.dirname(__file__), schemafile), 'r') as file: 108 content = file.read() 109 schemas = json.loads(content) 110 schema = schemas[schema_name] 111 validate(instance=metadata, schema=schema) #, format_checker=FormatChecker()) 112 print("Metadata validation successful!") 113 return True 114 115 except json.JSONDecodeError as e: 116 print("Failed to decode JSON:", e) 117 return False 118 119 except exceptions.ValidationError as e: 120 print("Validation error:", e) 121 return False 122 123 except FileNotFoundError: 124 print(f"No such file: {schemafile}") 125 return False 126 127 except Exception as e: 128 print("A general error occurred:", e) 129 return False 130 131 132def convert_datetime_in_dict(data): 133 """ 134 Recursively converts all datetime objects in a dictionary to strings in ISO format. 135 136 Args: 137 **data** (*dict*): The dictionary containing the data. 138 139 Returns: 140 *dict*: The dictionary with datetime objects converted to strings. 141 """ 142 for key, value in data.items(): 143 if isinstance(value, datetime): 144 data[key] = value.isoformat() 145 elif isinstance(value, dict): 146 data[key] = convert_datetime_in_dict(value) 147 return data 148 149 150def head(): 151 """ 152 Every library should have a nice ASCII art :) 153 Propose yours, there is a prize for the best one! 154 """ 155 print() 156 print(" HUMAN AUGMENTED BRAIN SYSTEMS ") 157 print(" ----------------------------------------------------------- ") 158 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒▒░ ▒▒▒▒▒▒▒▒▒▒▒▒░ ░▒▒▒▒▒▒▒▒▒░ ") 159 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒▒▒▒░ ░▒▒▒▒ ░▒▒▒░ ░▒░ ") 160 print(" ▒▒▒▒▒▒▒▒▒▒▒▒▒ ░▒▒▒▒ ▒▒▒▒░ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ░▒▒▒▒▒▒▒▒▒░ ") 161 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒ ▒▒▒▒░ ▒▒▒▒ ░▒▒▒▒ ░▒░ ░▒▒▒░ ") 162 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒ ▒▒▒▒░ ▒▒▒▒▒▒▒▒▒▒▒▒░ ░▒▒▒▒▒▒▒▒▒░ ") 163 print(" ----------------------------------------------------------- ") 164 print(" version:", version("HABSlib")) 165 print() 166 167 168###################################################### 169def handshake(base_url, user_id): 170 """ 171 Perform a handshake with the server to exchange encryption keys for the current session. 172 173 This function performs the following steps: 174 0. Performs login to the HABS server. 175 1. Sends a GET request to the server to initiate an RSA handshake. 176 2. Retrieves the server's public RSA key from the response. 177 3. Generates a local AES key and stores it in the environment. 178 4. Encrypts the AES key with the server's RSA key. 179 5. Sends the encrypted AES key to the server to complete the AES handshake. 180 181 Args: 182 **base_url** (*str*): The base URL of the server's API. 183 **user_id** (*str*): The user id (obtained through free registration with HABS) 184 185 Returns: 186 *bool*: True if the handshake is successful, None otherwise. 187 188 Raises: 189 **requests.RequestException**: If a request to the server fails. 190 191 Example: 192 ``` 193 success = handshake("https://example.com") 194 if success: 195 print("Handshake completed successfully.") 196 else: 197 print("Handshake failed.") 198 ``` 199 """ 200 head() 201 global BASE_URL 202 BASE_URL = base_url 203 url = f"{BASE_URL}/api/{VERSION}/handshake_rsa" 204 # response = requests.get(url) 205 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 206 207 if response.status_code == 200: 208 print("Handshake (RSA) successful.") 209 api_public_key_pem = response.json().get('api_public_key') 210 api_public_key = serialization.load_pem_public_key( 211 api_public_key_pem.encode(), 212 backend=default_backend() 213 ) 214 os.environ['API_PUBLIC_KEY'] = api_public_key_pem 215 216 # Then we generate and store the AES key 217 aes_key = generate_aes_key() 218 # print("aes_key", aes_key) 219 os.environ['AES_KEY'] = base64.b64encode( aes_key ).decode('utf-8') 220 221 encrypted_aes_key = encrypt_aes_key_with_rsa(aes_key, api_public_key) 222 encrypted_aes_key_b64 = base64.b64encode(encrypted_aes_key).decode('utf-8') 223 # print("encrypted_aes_key_b64",encrypted_aes_key_b64) 224 aes_key_payload = { 225 "encrypted_aes_key": encrypted_aes_key_b64 226 } 227 response = requests.post(f"{BASE_URL}/api/{VERSION}/handshake_aes", json=aes_key_payload, headers={'X-User-ID':user_id}) 228 229 if response.status_code == 200: 230 print("Handshake (AES) successful.") 231 return True 232 else: 233 print("Handshake (AES) failed:", response.text) 234 return None 235 else: 236 print("Handshake (RSA) failed:", response.text) 237 return None 238 239 240 241###################################################### 242def set_user(user_id, first_name=None, last_name=None, role=None, group=None, email=None, age=None, weight=None, gender=None): 243 """ 244 Creates a user by sending user data to the server. 245 246 This function performs the following steps: 247 1. Constructs the user data dictionary. 248 2. Validates the user data against the "userSchema". 249 3. Encrypts the user data using the stored AES key. 250 4. Sends the encrypted user data to the server. 251 5. Handles the server's response. 252 253 Args: 254 **user_id** (*str*): The user id (obtained through free registration with HABS) 255 **first_name** (*str*, optional): The user's first name. 256 **last_name** (*str*, optional): The user's last name. 257 **role** (*str*, required): The user's role (Admin, Developer, ... established at registration). 258 **group** (*str*, optional): The user's group (laboratory name, company name, ...). 259 **email** (*str*, required): The user's email address. 260 **age** (*int*, optional): The user's age. 261 **weight** (*float*, optional): The user's weight. 262 **gender** (*str*, optional): The user's gender. 263 264 Returns: 265 *str*: The user ID if the user is successfully created/retrieved, None otherwise. 266 267 Example: 268 ``` 269 user_id = set_user(first_name="John", last_name="Doe", email="john.doe@example.com", age=30, weight=70.5, gender="X") 270 if user_id: 271 print(f"User created/retrieved with ID: {user_id}") 272 else: 273 print("User creation failed.") 274 275 **NOTE**: In order to use this function, your role should be `Admin` 276 ``` 277 """ 278 url = f"{BASE_URL}/api/{VERSION}/users" 279 user_data = { 280 "first_name": first_name, 281 "last_name": last_name, 282 "role": role, 283 "group": group, 284 "email": email, 285 "age": age, 286 "weight": weight, 287 "gender": gender 288 } 289 if validate_metadata(user_data, "userSchema"): 290 _user = { 291 "user_data": user_data 292 } 293 _user = json.dumps(_user).encode('utf-8') 294 aes_key_b64 = os.environ.get('AES_KEY') 295 aes_key_bytes = base64.b64decode(aes_key_b64) 296 response = requests.post( 297 url, 298 data=encrypt_message(_user, aes_key_bytes), 299 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 300 ) 301 302 if response.status_code == 201 or response.status_code == 208: 303 print("User successfully created/retrieved.") 304 user_id = response.json().get('user_id') 305 return user_id 306 else: 307 print("User creation failed:", response.text) 308 return None 309 else: 310 print("User creation failed.") 311 312 313###################################################### 314def search_user_by_mail(user_id, email): 315 """ 316 Search for a user by email. 317 318 This function sends a GET request to the server to search for a user by the provided email address. 319 320 Args: 321 **user_id** (*str*): The user id (obtained through free registration with HABS) 322 **email** (*str*): The email address of the user to search for. 323 324 Returns: 325 *str*: The user ID if the user is found, None otherwise. 326 327 Example: 328 ``` 329 user_id = search_user_by_mail("john.doe@example.com") 330 if user_id: 331 print(f"User found with ID: {user_id}") 332 else: 333 print("User not found.") 334 ``` 335 """ 336 url = f"{BASE_URL}/api/{VERSION}/users?email={email}" 337 338 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 339 340 if response.status_code == 200: 341 user_id = response.json().get('user_id') 342 print("User found:", user_id) 343 return user_id 344 else: 345 print("User not found.", response.text) 346 return None 347 348 349###################################################### 350def get_user_by_id(user_id): 351 """ 352 Retrieve user data by user ID. 353 354 This function sends a GET request to the server to retrieve user data for the specified user ID. 355 The response data is decrypted using AES before returning the user data. 356 357 Args: 358 **user_id** (*str*): The unique identifier of the user to retrieve. 359 360 Returns: 361 *dict*: The user data if the user is found, None otherwise. 362 363 Example: 364 ``` 365 user_data = get_user_by_id("1234567890") 366 if user_data: 367 print(f"User data: {user_data}") 368 else: 369 print("User not found.") 370 ``` 371 """ 372 url = f"{BASE_URL}/api/{VERSION}/users/{user_id}" 373 374 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 375 376 if response.status_code == 200: 377 print("User found.") 378 encrypted_data = response.content 379 aes_key_b64 = os.environ.get('AES_KEY') 380 aes_key_bytes = base64.b64decode(aes_key_b64) 381 decrypted_json_string = decrypt_message(encrypted_data, aes_key_bytes) 382 user_data = json.loads(decrypted_json_string)['user_data'] 383 return user_data 384 else: 385 print("User not found:", response.text) 386 return None 387 388 389###################################################### 390def set_session(metadata, user_id): 391 """ 392 Create a new simple session. 393 394 This function sends a POST request to the server to create a new simple session using the provided metadata. 395 The metadata is encrypted using AES before being sent to the server. 396 397 Args: 398 **metadata** (*dict*): A dictionary containing the session metadata. The only required metadata for the simple session are the user_id and a date. 399 **user_id** (*str*): The user id (obtained through free registration with HABS) 400 401 Returns: 402 *str*: The unique identifier of the created session if successful, None otherwise. 403 404 Example: 405 ``` 406 session_metadata = { 407 "user_id": "1076203852085", 408 "session_date": "2024-05-30T12:00:00Z" 409 } 410 session_id = set_session(session_metadata) 411 if session_id: 412 print(f"Session created with ID: {session_id}") 413 else: 414 print("Failed to create session.") 415 ``` 416 """ 417 url = f"{BASE_URL}/api/{VERSION}/sessions" 418 _session = metadata 419 _session = json.dumps(_session).encode('utf-8') 420 aes_key_b64 = os.environ.get('AES_KEY') 421 aes_key_bytes = base64.b64decode(aes_key_b64) 422 response = requests.post( 423 url, 424 data=encrypt_message(_session, aes_key_bytes), 425 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 426 ) 427 428 if response.status_code == 200: 429 print("Session successfully created.") 430 # Extract the unique identifier for the uploaded data 431 session_id = response.json().get('session_id') 432 433 # print("session_id: ",session_id) 434 return session_id 435 else: 436 print("Session failed:", response.text) 437 return None 438 439 440###################################################### 441def get_data_by_id(data_id, user_id): 442 """ 443 Retrieve raw data by its unique identifier from the server. 444 445 This function sends a GET request to fetch raw data associated with a specific identifier. It 446 assumes that the data, if retrieved successfully, does not require decryption and is directly accessible. 447 448 Args: 449 **data_id** (*str*): The unique identifier for the data to be retrieved. 450 **user_id** (*str*): The user id (obtained through free registration with HABS) 451 452 Returns: 453 **dict**: The raw data if retrieval is successful, None otherwise. 454 455 Example: 456 ``` 457 data_id = "1234" 458 raw_data = get_data_by_id(data_id) 459 ... use the data 460 ``` 461 """ 462 url = f"{BASE_URL}/api/{VERSION}/rawdata/{data_id}" 463 464 # response = requests.get(url) 465 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 466 467 if response.status_code == 200: 468 print("Retrieved data successfully.") 469 # decrypt 470 return response.json().get('rawData') 471 else: 472 print("Failed to retrieve data:", response.text) 473 474 475 476###################################################### 477def find_sessions_by_user(user_id): 478 """ 479 Retrieve session IDs associated with a given user. 480 481 This function sends a GET request to the API to retrieve all session IDs for a specified user. 482 It expects the user ID to be passed as an argument and uses the user ID for authentication. 483 484 Args: 485 user_id (str): The user ID (obtained through free registration with HABS). 486 487 Returns: 488 list: A list of session IDs if the request is successful. 489 490 Raises: 491 Exception: If the request fails or if there is an error in the response. 492 493 Example: 494 >>> sessions = find_sessions_by_user("12345") 495 >>> print(sessions) 496 ["session1", "session2", "session3"] 497 498 Notes: 499 Ensure that the environment variable `AES_KEY` is set to the base64 encoded AES key. 500 """ 501 url = f"{BASE_URL}/api/{VERSION}/sessions/{user_id}" 502 503 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 504 505 if response.status_code == 200: 506 print("User found.") 507 encrypted_data = response.content 508 aes_key_b64 = os.environ.get('AES_KEY') 509 aes_key_bytes = base64.b64decode(aes_key_b64) 510 decrypted_json_string = decrypt_message(encrypted_data, aes_key_bytes) 511 session_ids = json.loads(decrypted_json_string)['session_ids'] 512 return session_ids 513 else: 514 print("Failed to retrieve data:", response.text) 515 516 517 518###################################################### 519def get_data_by_session(session_id, user_id): 520 """ 521 Retrieve raw data associated with a specific session identifier from the server. 522 523 This function sends a GET request to fetch all raw data linked to the given session ID. The data 524 is returned in its raw form assuming it does not require decryption for usage. 525 526 Args: 527 **session_id** (*str*): The session identifier whose associated data is to be retrieved. 528 **user_id** (*str*): The user id (obtained through free registration with HABS) 529 530 Returns: 531 *dict*: The raw data linked to the session if retrieval is successful, None otherwise. 532 533 Example: 534 ``` 535 session_id = "abcd1234" 536 session_data = get_data_by_session(session_id) 537 if session_data: 538 print("Data retrieved:", session_data) 539 else: 540 print("Failed to retrieve data.") 541 ``` 542 """ 543 url = f"{BASE_URL}/api/{VERSION}/sessions/{session_id}/rawdata" 544 545 # response = requests.get(url) 546 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 547 548 if response.status_code == 200: 549 print("Retrieved data successfully.") 550 # decrypt 551 return response.json().get('data') 552 else: 553 print("Failed to retrieve data:", response.text) 554 555 556 557###################################################### 558def get_data_ids_by_session(session_id, user_id): 559 """ 560 Retrieve a list of data IDs associated with a specific session from the server. 561 562 This function sends a GET request to fetch the IDs of all data entries linked to a specified session ID. 563 The IDs are returned as a list. The function assumes the data does not require decryption for usage. 564 565 Args: 566 **session_id** (*str*): The session identifier for which data IDs are to be retrieved. 567 **user_id** (*str*): The user id (obtained through free registration with HABS) 568 569 Returns: 570 *list*: A list of data IDs if retrieval is successful, None otherwise. 571 572 Example: 573 ``` 574 session_id = "abcd1234" 575 data_ids = get_data_ids_by_session(session_id) 576 if data_ids: 577 print("Data IDs retrieved:", data_ids) 578 else: 579 print("Failed to retrieve data IDs.") 580 ``` 581 """ 582 url = f"{BASE_URL}/api/{VERSION}/sessions/{session_id}/ids" 583 584 # response = requests.get(url) 585 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 586 587 if response.status_code == 200: 588 print("Retrieved ids successfully.") 589 # decrypt 590 return response.json().get('ids') 591 else: 592 print("Failed to retrieve ids:", response.text) 593 594 595 596###################################################### 597def upload_data(metadata, timestamps, user_id, data, ppg_red, ppg_ir): 598 """ 599 Uploads EEG (and PPG) data to the server along with associated metadata. 600 601 This function compiles different types of physiological data along with metadata into a single dictionary, 602 encrypts the data, and then uploads it via a POST request. Upon successful upload, the server returns a 603 unique identifier for the data which can then be used for future queries or operations. 604 605 Args: 606 **metadata** (*dict*): Information about the data such as subject details and session parameters. 607 **timestamps** (*list*): List of timestamps correlating with each data point. 608 **user_id** (*str*): The user id (obtained through free registration with HABS) 609 **data** (*list*): EEG data points. 610 **ppg_red** (*list*): Red photoplethysmogram data points. 611 **ppg_ir** (*list*): Infrared photoplethysmogram data points. 612 613 Returns: 614 *tuple*: A tuple containing the data ID of the uploaded data if successful, and None otherwise. 615 616 Notes: 617 Ensure that timestamps has the same length of data last dimension. 618 619 Example: 620 ``` 621 metadata = {"session_id": "1234", "subject_id": "001"} 622 timestamps = [1597709184, 1597709185] 623 data = [0.1, 0.2] 624 ppg_red = [123, 124] 625 ppg_ir = [125, 126] 626 data_id, error = upload_data(metadata, timestamps, data, ppg_red, ppg_ir) 627 if data_id: 628 print("Data uploaded successfully. Data ID:", data_id) 629 else: 630 print("Upload failed with error:", error) 631 ``` 632 """ 633 url = f"{BASE_URL}/api/{VERSION}/rawdata" 634 _data = { 635 "metadata": metadata, 636 "timestamps": timestamps, 637 "data": data, 638 "ppg_red": ppg_red, 639 "ppg_ir": ppg_ir 640 } 641 _data = json.dumps(_data).encode('utf-8') 642 643 # response = requests.post(url, json=_data) 644 aes_key_b64 = os.environ.get('AES_KEY') 645 aes_key_bytes = base64.b64decode(aes_key_b64) 646 response = requests.post( 647 url, 648 data=encrypt_message(_data, aes_key_bytes), 649 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 650 ) 651 # response = requests.get(url, headers={'X-User-ID':USERID}) # mongo _id for the user document. Communicated at user creation. 652 653 if response.status_code == 200: 654 print('.', end='', flush=True) 655 # Extract the unique identifier for the uploaded data 656 data_id = response.json().get('data_id') 657 return data_id, None 658 else: 659 print("Upload failed:", response.text) 660 return None 661 662 663 664###################################################### 665def acquire_send_raw(user_id, date, board, serial_number, stream_duration, buffer_duration, session_type="", tags=[], callback=None, extra=None): 666 """ 667 Asynchronously acquires raw data from a specific EEG board and sends it to the server. 668 669 This function connects to an EEG board, initiates a data acquisition session, and sends the collected data 670 to the server in real-time or near real-time. It ensures that all the data handled during the session 671 is associated with a unique session ID and metadata that includes user and session details. The function 672 will validate the session metadata before proceeding with data acquisition and sending. 673 674 Args: 675 **user_id** (*str*): The unique identifier of the user for whom the data is being collected. 676 **date** (*str*): The date of the session, used for metadata purposes. 677 **board** (*int*): Identifier for the EEG board from which data will be acquired. 678 **stream_duration** (*int*): Duration in seconds for which data will be streamed from the board. 679 **buffer_duration** (*int*): Time in seconds for how often the data is buffered and sent. 680 681 Returns: 682 *str* or *bool*: The session ID if the operation is successful; False otherwise. 683 684 Raises: 685 **ConnectionError**: If the board connection fails. 686 **ValidationError**: If the metadata does not comply with the required schema. 687 688 Example: 689 ``` 690 session = acquire_send_raw('user123', '2021-06-01', 'MUSE_S', 300, 10) 691 if session: 692 print(f"Session successfully started with ID: {session}") 693 else: 694 print("Failed to start session") 695 ``` 696 """ 697 # set session for the data 698 # We set a session id for the current interaction with the API (even if we fail to get the board, it will be important to store the failure) 699 session_metadata = { 700 "user_id": user_id, # add user to the session for reference 701 "session_date": date, 702 "session_type": session_type, 703 "session_tags": tags 704 } 705 session_id = set_session(metadata={**session_metadata}, user_id=user_id) 706 print("\nSession initialized. You can visualize it here:\n ", "https://habs.ai/live.html?session_id="+str(session_id), "\n") 707 708 if validate_metadata(session_metadata, "sessionSchema"): 709 asyncio.run( 710 _acquire_send_raw(user_id, session_id, board, serial_number, stream_duration, buffer_duration, callback, extra) 711 ) 712 return session_id 713 else: 714 print("Session initialization failed.") 715 return False 716 717# async appendage 718async def _acquire_send_raw(user_id, session_id, board, serial_number, stream_duration, buffer_duration, callback=None, extra=None): 719 # get board 720 board_manager = BoardManager(enable_logger=False, board_id=board, serial_number=serial_number, extra=extra) 721 board_manager.connect() 722 723 board_manager.metadata['session_id'] = session_id # add session to the data for reference 724 725 # stream_duration sec, buffer_duration sec 726 await board_manager.data_acquisition_loop( 727 stream_duration=stream_duration, 728 buffer_duration=buffer_duration, 729 service=upload_data, 730 user_id=user_id, 731 callback=callback 732 ) 733 734 735 736###################################################### 737def send_file(user_id, date, edf_file, ch_nrs=None, ch_names=None, session_type="", tags=[]): 738 """ 739 Uploads EEG data from a file to the server along with associated metadata. 740 741 This function compiles EEG data from an [EDF file](https://www.edfplus.info/downloads/index.html) along with metadata into a single dictionary, 742 encrypts the data, and then uploads it via a POST request. Upon successful upload, the server returns a 743 unique identifier for the session which can then be used for future queries or operations. 744 745 Args: 746 **user_id** (*str*): The unique identifier of the user for whom the data is being collected. 747 **metadata** (*dict*): Information about the data such as subject details and session parameters. 748 **date** (*str*): The date of the session, used for metadata purposes. 749 **edf_file** (*str*): name of an EDF file. 750 **ch_nrs** (*list of int*, optional): The indices of the channels to read. The default is None. 751 **ch_names** (*list of str*, optional): The names of channels to read. The default is None. 752 753 Returns: 754 *tuple*: A tuple containing the session ID of the uploaded data if successful, and None otherwise. 755 756 Example: 757 ``` 758 session = send_file('user123', '2021-06-01', 'nameoffile.edf') 759 if session: 760 print(f"Session successfully started with ID: {session}") 761 else: 762 print("Failed to start session") 763 ``` 764 """ 765 766 try: 767 signals, signal_headers, header = highlevel.read_edf(edf_file, ch_nrs, ch_names) 768 769 max_time = signals.shape[1] / signal_headers[0]['sample_frequency'] 770 timestamps = np.linspace(header['startdate'].timestamp(), max_time, signals.shape[1]) 771 772 session_metadata = { 773 "user_id": user_id, # add user to the session for reference 774 "session_date": header['startdate'].strftime("%m/%d/%Y, %H:%M:%S"), 775 "session_type": session_type, 776 "session_tags": tags 777 } 778 if validate_metadata(session_metadata, "sessionSchema"): 779 session_id = set_session(metadata={**session_metadata}, user_id=user_id) 780 metadata = {'session_id':session_id, **session_metadata, **convert_datetime_in_dict(header), **convert_datetime_in_dict(signal_headers[0])} 781 782 chunks = ((signals.size * signals.itemsize)//300000)+1 783 timestamps_chunks = np.array_split(timestamps, chunks) 784 signals_chunks = np.array_split(signals, chunks, axis=1) 785 json_data = json.dumps(signals_chunks[0].tolist()) 786 size_in_bytes = sys.getsizeof(json_data) 787 print("%d total bytes will be sent into %d chunks of %d bytes" % (signals.size * signals.itemsize, chunks, size_in_bytes)) 788 789 for timestamps_chunk,signals_chunk in zip(timestamps_chunks, signals_chunks): 790 upload_data(metadata, timestamps_chunk.tolist(), user_id, signals_chunk.tolist(), [], []) 791 792 return session_id 793 else: 794 return False 795 except Exception as e: 796 print("A general error occurred:", e) 797 return False 798 799 800 801###################################################### 802###################################################### 803def set_pipe(metadata, pipeline, params, user_id): 804 """ 805 Configures and initiates a data processing pipeline for a session on the server. 806 807 This function sends metadata and processing parameters to a specified pipeline endpoint 808 to create a data processing session. It encrypts the session data before sending to ensure 809 security. The function checks the server response to confirm the session creation. 810 811 Args: 812 **metadata** (*dict*): A dictionary containing metadata about the session, typically including 813 details such as user ID and session date. 814 **pipeline** (*str*): The identifier for the processing pipeline to be used. 815 **params** (*dict*): Parameters specific to the processing pipeline, detailing how data should 816 be processed. 817 **user_id** (*str*): The user id (obtained through free registration with HABS) 818 819 Returns: 820 *str* or *None*: The session ID if the session is successfully created, or None if the operation fails. 821 822 Raises: 823 **requests.exceptions.RequestException**: An error from the Requests library when an HTTP request fails. 824 **KeyError**: If necessary keys are missing in the environment variables. 825 826 Example: 827 ``` 828 session_metadata = {"user_id": "123", "session_date": "2024-06-03"} 829 processing_params = {"filter_type": "lowpass", "cutoff_freq": 30} 830 session_id = set_pipe(session_metadata, 'eeg_smoothing', processing_params) 831 if session_id: 832 print(f"Pipeline session created with ID: {session_id}") 833 else: 834 print("Failed to create pipeline session") 835 ``` 836 """ 837 url = f"{BASE_URL}/api/{VERSION}/sessions/pipe/{pipeline}" 838 _session = { 839 "metadata": metadata, 840 "processing_params": params, 841 } 842 _session = json.dumps(_session).encode('utf-8') 843 aes_key_b64 = os.environ.get('AES_KEY') 844 aes_key_bytes = base64.b64decode(aes_key_b64) 845 response = requests.post( 846 url, 847 data=encrypt_message(_session, aes_key_bytes), 848 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 849 ) 850 if response.status_code == 200: 851 print("Session successfully created.") 852 # Extract the unique identifier for the uploaded data 853 session_id = response.json().get('session_id') 854 # print(session_id) 855 return session_id 856 else: 857 print("Session failed:", response.text) 858 return None 859 860 861 862###################################################### 863def upload_pipedata(metadata, timestamps, user_id, data, ppg_red, ppg_ir): 864 """ 865 Uploads processed data to a specific session on the server. 866 867 This function is responsible for uploading various data streams associated with a session, including 868 timestamps and physiological measurements such as PPG (Photoplethysmogram). The data is encrypted before 869 sending to ensure confidentiality and integrity. 870 871 Args: 872 **metadata** (*dict*): Contains session-related metadata including the session ID. 873 **timestamps** (*list*): A list of timestamps corresponding to each data point. 874 **user_id** (*str*): The user id (obtained through free registration with HABS) 875 **data** (*list*): The main data collected, e.g., EEG readings. 876 **ppg_red** (*list*): Red channel data from a PPG sensor. 877 **ppg_ir** (*list*): Infrared channel data from a PPG sensor. 878 879 Returns: 880 *tuple*: A tuple containing the data ID if the upload is successful and the processed data, or None if the upload fails. 881 882 Raises: 883 **requests.exceptions.RequestException: An error from the Requests library when an HTTP request fails. 884 **KeyError**: If necessary keys are missing in the environment variables. 885 886 Example: 887 ``` 888 session_metadata = {"session_id": "12345"} 889 timestamps = [1597709165, 1597709166, ...] 890 data = [0.1, 0.2, ...] 891 ppg_red = [12, 15, ...] 892 ppg_ir = [20, 22, ...] 893 data_id, processed_data = upload_pipedata(session_metadata, timestamps, data, ppg_red, ppg_ir) 894 if data_id: 895 print(f"Data uploaded successfully with ID: {data_id}") 896 else: 897 print("Failed to upload data") 898 ``` 899 """ 900 url = f"{BASE_URL}/api/{VERSION}/pipedata/{metadata['session_id']}" # the metadata contain session_id to consistently pass it with each upload 901 902 _data = { 903 "metadata": metadata, 904 "timestamps": timestamps, 905 "data": data, 906 "ppg_red": ppg_red, 907 "ppg_ir": ppg_ir 908 } 909 _data = json.dumps(_data).encode('utf-8') 910 aes_key_b64 = os.environ.get('AES_KEY') 911 aes_key_bytes = base64.b64decode(aes_key_b64) 912 response = requests.post( 913 url, 914 data=encrypt_message(_data, aes_key_bytes), 915 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 916 ) 917 918 if response.status_code == 200: 919 print('.', end='', flush=True) 920 # Extract the unique identifier for the uploaded data 921 data_id = response.json().get('data_id') 922 # Retrieve the processed data 923 data = response.json().get('pipeData') 924 return data_id, data 925 else: 926 print("Upload failed:", response.text) 927 return None 928 929 930###################################################### 931def acquire_send_pipe(pipeline, params, user_id, date, board, serial_number, stream_duration, buffer_duration, session_type="", tags=[], callback=None, extra=None): 932 """ 933 Acquires data from a board, processes it according to a specified pipeline, and sends it to a server. 934 This function handles setting up a session for data acquisition and processing, connects to a board, 935 and manages the data flow from acquisition through processing to uploading. It uses an asynchronous loop 936 to handle the operations efficiently, suitable for real-time data processing scenarios. 937 938 Args: 939 **pipeline** (*str*): Name of the processing pipeline to use. 940 **params** (*dict*): Parameters for the pipeline processing. 941 **user_id** (*str*): The user ID to which the session will be associated. 942 **date** (*str*): Date of the session for tracking purposes. 943 **board** (*int*): Identifier for the hardware board to use for data acquisition. 944 **stream_duration** (*int*): Duration in seconds to stream data from the board. 945 **buffer_duration** (*int*): Duration in seconds to buffer data before processing. 946 **callback** (*function*): Optional callback function to execute after data is sent. 947 948 Returns: 949 *str* or *bool*: The session ID if successful, False otherwise. 950 951 """ 952 # set session for the data 953 # We set a session id for the current interaction with the API (even if we fail to get the board, it will be important to store the failure) 954 session_metadata = { 955 "user_id": user_id, # add user to the session for reference 956 "session_date": date, 957 "session_type": session_type, 958 "session_tags": tags 959 } 960 if validate_metadata(session_metadata, "sessionSchema"): 961 session_id = set_pipe(metadata={**session_metadata}, pipeline=pipeline, params=params, user_id=user_id) 962 print("\nSession initialized. You can visualize it here:\n ", "https://habs.ai/bos/live.html?session_id="+str(session_id), "\n") 963 964 asyncio.run( 965 _acquire_send_pipe(pipeline, params, user_id, session_id, board, serial_number, stream_duration, buffer_duration, callback, extra) 966 ) 967 return session_id #, self.processed_data 968 else: 969 print("Session initialization failed.") 970 return False 971 972# async appendage 973async def _acquire_send_pipe(pipeline, params, user_id, session_id, board, serial_number, stream_duration, buffer_duration, callback=None, extra=None): 974 # get board 975 board_manager = BoardManager(enable_logger=False, board_id=board, serial_number=serial_number, extra=extra) 976 board_manager.connect() 977 978 board_manager.metadata['session_id'] = session_id # add session to the data for reference 979 980 # stream_duration sec, buffer_duration sec 981 await board_manager.data_acquisition_loop( 982 stream_duration=stream_duration, 983 buffer_duration=buffer_duration, 984 service=upload_pipedata, 985 user_id=user_id, 986 callback=callback 987 ) 988 989 990 991 992###################################################### 993def get_user_database(user_id): 994 """ 995 Retrieve all user data by user ID. 996 997 This function sends a GET request to the server to dump all data stored for the specified user ID. 998 The response data is decrypted using AES before returning the user data. 999 1000 Args: 1001 **user_id** (*str*): The unique identifier of the user to retrieve. 1002 1003 Returns: 1004 *None*: A zip file contaning all data as JSON files, None otherwise. 1005 1006 Example: 1007 ``` 1008 user_data = get_user_database("1234567890") 1009 if user_data: 1010 print(f"User data: {user_data}") 1011 else: 1012 print("User not found.") 1013 ``` 1014 """ 1015 url = f"{BASE_URL}/api/{VERSION}/database/dump/{user_id}" 1016 1017 response = requests.get(url, headers={'X-User-ID': user_id}, stream=True) 1018 1019 if response.status_code == 200: 1020 # Open a local file with write-binary mode 1021 strtime = datetime.today().strftime("%Y%m%d_%H%M%S") 1022 1023 with open(f"brainos_{strtime}_dump.zip", 'wb') as file: 1024 # Write the response content to the file in chunks 1025 for chunk in response.iter_content(chunk_size=8192): 1026 file.write(chunk) 1027 print("Database dump saved successfully.") 1028 return True 1029 else: 1030 print("User not found:", response.text) 1031 return None 1032 1033 1034 1035 1036###################################################### 1037def create_tagged_interval(user_id, session_id, eeg_data_id, start_time, end_time, tags, channel_ids=None): 1038 """ 1039 Creates a tagged interval by sending the interval data to the server. 1040 1041 This function performs the following steps: 1042 1. Constructs the interval data dictionary. 1043 2. Validates the interval data against the "tagSchema". 1044 3. Sends the interval data to the server. 1045 1046 Args: 1047 **session_id** (*str*): The session id. 1048 **eeg_data_id** (*str*): The EEG data id. 1049 **start_time** (*str*): The start time of the interval in ISO 8601 format. 1050 **end_time** (*str*): The end time of the interval in ISO 8601 format. 1051 **tags** (*list*): List of tags, each tag is a dictionary containing a "tag" and "properties". 1052 **channel_ids** (*list*, optional): List of channel ids the tag applies to. If None, applies to all channels. 1053 1054 Returns: 1055 *str*: The interval ID if the interval is successfully created, None otherwise. 1056 1057 Example: 1058 ``` 1059 interval_id = create_tagged_interval( 1060 session_id="session_123", 1061 eeg_data_id="eeg_data_456", 1062 start_time="2023-01-01T00:00:00Z", 1063 end_time="2023-01-01T00:05:00Z", 1064 tags=[{"tag": "seizure", "properties": {"severity": "high"}}] 1065 ) 1066 if interval_id: 1067 print(f"Tagged interval created with ID: {interval_id}") 1068 else: 1069 print("Tagged interval creation failed.") 1070 ``` 1071 """ 1072 url = f"{BASE_URL}/api/{VERSION}/session/{session_id}/tag" 1073 interval_data = { 1074 "user_id": user_id, 1075 "session_id": session_id, 1076 "eeg_data_id": eeg_data_id, 1077 "start_time": start_time, 1078 "end_time": end_time, 1079 "tags": tags, 1080 "channel_ids": channel_ids if channel_ids else [] 1081 } 1082 1083 if validate_metadata(interval_data, "tagSchema"): 1084 response = requests.post( 1085 url, 1086 json=interval_data, 1087 headers={'Content-Type': 'application/json'} 1088 ) 1089 1090 if response.status_code == 201: 1091 print("Tagged interval successfully created.") 1092 interval_id = response.json().get('interval_id') 1093 return interval_id 1094 else: 1095 print("Tagged interval creation failed:", response.text) 1096 return None 1097 else: 1098 print("Tagged interval creation failed due to validation error.") 1099 1100 1101 1102 1103 1104###################################################### 1105def process_session_pipe(pipeline, params, user_id, date, existing_session_id, session_type="", tags=[]): 1106 """ 1107 Process a session pipeline with specified parameters and metadata. 1108 1109 This function processes an existing session by applying a specified pipeline and parameters. 1110 It sends a POST request to the API with the session metadata and processing parameters, 1111 creating a new session based on the existing one. 1112 1113 Args: 1114 **pipeline** (*str*): The pipeline to be applied to the session. 1115 **params** (*dict*): The processing parameters for the pipeline. 1116 **user_id** (*str*): The user ID (obtained through free registration with HABS). 1117 **date** (*str*): The date of the session. 1118 **existing_session_id** (*str*): The ID of the existing session to be processed. 1119 **session_type** (*str*, optional): The type of the new session. Defaults to an empty string. 1120 **tags** (*list*, optional): A list of tags associated with the session. Defaults to an empty list. 1121 1122 Returns: 1123 tuple: A tuple containing the new session ID and the processed data if the request is successful. 1124 None: If the session creation fails. 1125 bool: False if the session metadata is invalid. 1126 1127 Example: 1128 >>> new_session_id, processed_data = process_session_pipe("my_pipeline", {"param1": "value1"}, "12345", "2023-07-03", "existing_session_001") 1129 >>> print(new_session_id, processed_data) 1130 1131 Notes: 1132 Ensure that the environment variable `AES_KEY` is set to the base64 encoded AES key. 1133 1134 Raises: 1135 Exception: If there is an error in the request or response. 1136 1137 """ 1138 session_metadata = { 1139 "user_id": user_id, # add user to the session for reference 1140 "session_date": date, # .strftime("%m/%d/%Y, %H:%M:%S"), 1141 "existing_session_id": existing_session_id, 1142 "session_type": f"[On {existing_session_id}]: {session_type}", # type of the new session 1143 "session_tags": tags 1144 } 1145 if validate_metadata(session_metadata, "sessionSchema"): 1146 url = f"{BASE_URL}/api/{VERSION}/sessions/{existing_session_id}/pipe/{pipeline}" 1147 _session = { 1148 "metadata": session_metadata, 1149 "processing_params": params, 1150 } 1151 _session = json.dumps(_session).encode('utf-8') 1152 aes_key_b64 = os.environ.get('AES_KEY') 1153 aes_key_bytes = base64.b64decode(aes_key_b64) 1154 response = requests.post( 1155 url, 1156 data=encrypt_message(_session, aes_key_bytes), 1157 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 1158 ) 1159 if response.status_code == 200: 1160 print("Session successfully created.") 1161 session_id = response.json().get('session_id') 1162 pipeData = response.json().get('pipeData') 1163 # print(session_id) 1164 return session_id, pipeData 1165 else: 1166 print("Session failed:", response.text) 1167 return None 1168 1169 return session_id # processed_data 1170 else: 1171 print("Session failed.") 1172 return False 1173 1174 1175 1176 1177###################################################### 1178def train(session_id, params, user_id): 1179 """ 1180 Sends a request to the server to train a machine learning algorithm on the data from a specified session. 1181 1182 Args: 1183 **session_id** (*str*): The unique identifier of the session containing the data to be used for training. 1184 **params** (*dict*): The parameters for the training process. 1185 **user_id** (*str*): The user id (obtained through free registration with HABS) 1186 1187 Returns: 1188 *str* or *None*: The task ID if the request is successful, None otherwise. 1189 1190 This function sends the training parameters and session ID to the server, which initiates the training process. 1191 The response includes a task ID that can be used for future interactions related to the training task. 1192 1193 Example: 1194 ``` 1195 train("session_12345", {"param1": "value1", "param2": "value2"}) 1196 ``` 1197 """ 1198 url = f"{BASE_URL}/api/{VERSION}/train/{session_id}" 1199 _params = { 1200 "params": params, 1201 } 1202 _params = json.dumps(_params).encode('utf-8') 1203 aes_key_b64 = os.environ.get('AES_KEY') 1204 aes_key_bytes = base64.b64decode(aes_key_b64) 1205 response = requests.post( 1206 url, 1207 data=encrypt_message(_params, aes_key_bytes), 1208 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 1209 ) 1210 # response = requests.get(url, headers={'X-User-ID':USERID}) # mongo _id for the user document. Communicated at user creation. 1211 1212 if response.status_code == 200: 1213 task_id = response.json().get('task_id') 1214 print("Published. For future interactions, use task_id:",task_id) 1215 return task_id 1216 else: 1217 print("Publish failed:", response.text) 1218 return None 1219 1220 1221 1222 1223###################################################### 1224def infer(data_id, params, user_id): 1225 """ 1226 Sends a request to the server to perform machine learning inference based on a previously trained model, given the data ID. 1227 1228 Args: 1229 **data_id** (*str*): The unique identifier of the data to be used for inference. 1230 **params** (*dict*): The parameters for the inference process. 1231 **user_id** (*str*): The user id (obtained through free registration with HABS) 1232 1233 Returns: 1234 *str* or *None*: The task ID if the request is successful, None otherwise. 1235 1236 This function sends the inference parameters and data ID to the server, which initiates the inference process. 1237 The response includes a task ID that can be used for future interactions related to the inference task. 1238 1239 Example: 1240 ``` 1241 infer("data_12345", {"param1": "value1", "param2": "value2"}) 1242 ``` 1243 """ 1244 url = f"{BASE_URL}/api/{VERSION}/infer/{data_id}" 1245 _params = { 1246 "params": params, 1247 } 1248 _params = json.dumps(_params).encode('utf-8') 1249 # response = requests.post(url, json=_params) 1250 aes_key_b64 = os.environ.get('AES_KEY') 1251 aes_key_bytes = base64.b64decode(aes_key_b64) 1252 response = requests.post( 1253 url, 1254 data=encrypt_message(_params, aes_key_bytes), 1255 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 1256 ) 1257 # response = requests.get(url, headers={}) # mongo _id for the user document. Communicated at user creation. 1258 1259 if response.status_code == 200: 1260 task_id = response.json().get('task_id') 1261 print("Published. For future interactions, use task_id:",task_id) 1262 return task_id 1263 else: 1264 print("Publish failed:", response.text) 1265 return None
77def validate_metadata(metadata, schema_name, schemafile='metadata.json'): 78 """ 79 Validate metadata against a given JSON schema. 80 81 Args: 82 **metadata** (*dict*): The metadata to be validated. 83 **schema_name** (*str*): The name of the schema to validate against. HABSlib currently supports the validation of Session metadata and User data. 84 **schemafile** (*str*, optional): The path to the JSON file containing the schemas. Defaults to 'metadata.json'. 85 86 Returns: 87 *bool*: True if validation is successful, False otherwise. 88 89 Raises: 90 **FileNotFoundError**: If the schema file does not exist. 91 **json.JSONDecodeError**: If there is an error decoding the JSON schema file. 92 **exceptions.ValidationError**: If the metadata does not conform to the schema. 93 **Exception**: For any other errors that occur during validation. 94 95 Example: 96 ``` 97 metadata = {"name": "example", "type": "data"} 98 schema_name = "example_schema" 99 is_valid = validate_metadata(metadata, schema_name) 100 if is_valid: 101 print("Metadata is valid.") 102 else: 103 print("Metadata is invalid.") 104 ``` 105 """ 106 print(metadata) 107 try: 108 with open(os.path.join(os.path.dirname(__file__), schemafile), 'r') as file: 109 content = file.read() 110 schemas = json.loads(content) 111 schema = schemas[schema_name] 112 validate(instance=metadata, schema=schema) #, format_checker=FormatChecker()) 113 print("Metadata validation successful!") 114 return True 115 116 except json.JSONDecodeError as e: 117 print("Failed to decode JSON:", e) 118 return False 119 120 except exceptions.ValidationError as e: 121 print("Validation error:", e) 122 return False 123 124 except FileNotFoundError: 125 print(f"No such file: {schemafile}") 126 return False 127 128 except Exception as e: 129 print("A general error occurred:", e) 130 return False
Validate metadata against a given JSON schema.
Args:
metadata (dict): The metadata to be validated.
schema_name (str): The name of the schema to validate against. HABSlib currently supports the validation of Session metadata and User data.
schemafile (str, optional): The path to the JSON file containing the schemas. Defaults to 'metadata.json'.
Returns:
bool: True if validation is successful, False otherwise.
Raises:
FileNotFoundError: If the schema file does not exist.
json.JSONDecodeError: If there is an error decoding the JSON schema file.
exceptions.ValidationError: If the metadata does not conform to the schema.
Exception: For any other errors that occur during validation.
Example:
metadata = {"name": "example", "type": "data"}
schema_name = "example_schema"
is_valid = validate_metadata(metadata, schema_name)
if is_valid:
print("Metadata is valid.")
else:
print("Metadata is invalid.")
133def convert_datetime_in_dict(data): 134 """ 135 Recursively converts all datetime objects in a dictionary to strings in ISO format. 136 137 Args: 138 **data** (*dict*): The dictionary containing the data. 139 140 Returns: 141 *dict*: The dictionary with datetime objects converted to strings. 142 """ 143 for key, value in data.items(): 144 if isinstance(value, datetime): 145 data[key] = value.isoformat() 146 elif isinstance(value, dict): 147 data[key] = convert_datetime_in_dict(value) 148 return data
Recursively converts all datetime objects in a dictionary to strings in ISO format.
Args:
data (dict): The dictionary containing the data.
Returns:
dict: The dictionary with datetime objects converted to strings.
151def head(): 152 """ 153 Every library should have a nice ASCII art :) 154 Propose yours, there is a prize for the best one! 155 """ 156 print() 157 print(" HUMAN AUGMENTED BRAIN SYSTEMS ") 158 print(" ----------------------------------------------------------- ") 159 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒▒░ ▒▒▒▒▒▒▒▒▒▒▒▒░ ░▒▒▒▒▒▒▒▒▒░ ") 160 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒▒▒▒░ ░▒▒▒▒ ░▒▒▒░ ░▒░ ") 161 print(" ▒▒▒▒▒▒▒▒▒▒▒▒▒ ░▒▒▒▒ ▒▒▒▒░ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ░▒▒▒▒▒▒▒▒▒░ ") 162 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒ ▒▒▒▒░ ▒▒▒▒ ░▒▒▒▒ ░▒░ ░▒▒▒░ ") 163 print(" ▒▒▒▒ ▒▒▒▒ ░▒▒▒▒ ▒▒▒▒░ ▒▒▒▒▒▒▒▒▒▒▒▒░ ░▒▒▒▒▒▒▒▒▒░ ") 164 print(" ----------------------------------------------------------- ") 165 print(" version:", version("HABSlib")) 166 print()
Every library should have a nice ASCII art :) Propose yours, there is a prize for the best one!
170def handshake(base_url, user_id): 171 """ 172 Perform a handshake with the server to exchange encryption keys for the current session. 173 174 This function performs the following steps: 175 0. Performs login to the HABS server. 176 1. Sends a GET request to the server to initiate an RSA handshake. 177 2. Retrieves the server's public RSA key from the response. 178 3. Generates a local AES key and stores it in the environment. 179 4. Encrypts the AES key with the server's RSA key. 180 5. Sends the encrypted AES key to the server to complete the AES handshake. 181 182 Args: 183 **base_url** (*str*): The base URL of the server's API. 184 **user_id** (*str*): The user id (obtained through free registration with HABS) 185 186 Returns: 187 *bool*: True if the handshake is successful, None otherwise. 188 189 Raises: 190 **requests.RequestException**: If a request to the server fails. 191 192 Example: 193 ``` 194 success = handshake("https://example.com") 195 if success: 196 print("Handshake completed successfully.") 197 else: 198 print("Handshake failed.") 199 ``` 200 """ 201 head() 202 global BASE_URL 203 BASE_URL = base_url 204 url = f"{BASE_URL}/api/{VERSION}/handshake_rsa" 205 # response = requests.get(url) 206 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 207 208 if response.status_code == 200: 209 print("Handshake (RSA) successful.") 210 api_public_key_pem = response.json().get('api_public_key') 211 api_public_key = serialization.load_pem_public_key( 212 api_public_key_pem.encode(), 213 backend=default_backend() 214 ) 215 os.environ['API_PUBLIC_KEY'] = api_public_key_pem 216 217 # Then we generate and store the AES key 218 aes_key = generate_aes_key() 219 # print("aes_key", aes_key) 220 os.environ['AES_KEY'] = base64.b64encode( aes_key ).decode('utf-8') 221 222 encrypted_aes_key = encrypt_aes_key_with_rsa(aes_key, api_public_key) 223 encrypted_aes_key_b64 = base64.b64encode(encrypted_aes_key).decode('utf-8') 224 # print("encrypted_aes_key_b64",encrypted_aes_key_b64) 225 aes_key_payload = { 226 "encrypted_aes_key": encrypted_aes_key_b64 227 } 228 response = requests.post(f"{BASE_URL}/api/{VERSION}/handshake_aes", json=aes_key_payload, headers={'X-User-ID':user_id}) 229 230 if response.status_code == 200: 231 print("Handshake (AES) successful.") 232 return True 233 else: 234 print("Handshake (AES) failed:", response.text) 235 return None 236 else: 237 print("Handshake (RSA) failed:", response.text) 238 return None
Perform a handshake with the server to exchange encryption keys for the current session.
This function performs the following steps:
- Performs login to the HABS server.
- Sends a GET request to the server to initiate an RSA handshake.
- Retrieves the server's public RSA key from the response.
- Generates a local AES key and stores it in the environment.
- Encrypts the AES key with the server's RSA key.
- Sends the encrypted AES key to the server to complete the AES handshake.
Args:
base_url (str): The base URL of the server's API.
user_id (str): The user id (obtained through free registration with HABS)
Returns:
bool: True if the handshake is successful, None otherwise.
Raises:
requests.RequestException: If a request to the server fails.
Example:
success = handshake("https://example.com")
if success:
print("Handshake completed successfully.")
else:
print("Handshake failed.")
243def set_user(user_id, first_name=None, last_name=None, role=None, group=None, email=None, age=None, weight=None, gender=None): 244 """ 245 Creates a user by sending user data to the server. 246 247 This function performs the following steps: 248 1. Constructs the user data dictionary. 249 2. Validates the user data against the "userSchema". 250 3. Encrypts the user data using the stored AES key. 251 4. Sends the encrypted user data to the server. 252 5. Handles the server's response. 253 254 Args: 255 **user_id** (*str*): The user id (obtained through free registration with HABS) 256 **first_name** (*str*, optional): The user's first name. 257 **last_name** (*str*, optional): The user's last name. 258 **role** (*str*, required): The user's role (Admin, Developer, ... established at registration). 259 **group** (*str*, optional): The user's group (laboratory name, company name, ...). 260 **email** (*str*, required): The user's email address. 261 **age** (*int*, optional): The user's age. 262 **weight** (*float*, optional): The user's weight. 263 **gender** (*str*, optional): The user's gender. 264 265 Returns: 266 *str*: The user ID if the user is successfully created/retrieved, None otherwise. 267 268 Example: 269 ``` 270 user_id = set_user(first_name="John", last_name="Doe", email="john.doe@example.com", age=30, weight=70.5, gender="X") 271 if user_id: 272 print(f"User created/retrieved with ID: {user_id}") 273 else: 274 print("User creation failed.") 275 276 **NOTE**: In order to use this function, your role should be `Admin` 277 ``` 278 """ 279 url = f"{BASE_URL}/api/{VERSION}/users" 280 user_data = { 281 "first_name": first_name, 282 "last_name": last_name, 283 "role": role, 284 "group": group, 285 "email": email, 286 "age": age, 287 "weight": weight, 288 "gender": gender 289 } 290 if validate_metadata(user_data, "userSchema"): 291 _user = { 292 "user_data": user_data 293 } 294 _user = json.dumps(_user).encode('utf-8') 295 aes_key_b64 = os.environ.get('AES_KEY') 296 aes_key_bytes = base64.b64decode(aes_key_b64) 297 response = requests.post( 298 url, 299 data=encrypt_message(_user, aes_key_bytes), 300 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 301 ) 302 303 if response.status_code == 201 or response.status_code == 208: 304 print("User successfully created/retrieved.") 305 user_id = response.json().get('user_id') 306 return user_id 307 else: 308 print("User creation failed:", response.text) 309 return None 310 else: 311 print("User creation failed.")
Creates a user by sending user data to the server.
This function performs the following steps:
- Constructs the user data dictionary.
- Validates the user data against the "userSchema".
- Encrypts the user data using the stored AES key.
- Sends the encrypted user data to the server.
- Handles the server's response.
Args:
user_id (str): The user id (obtained through free registration with HABS)
first_name (str, optional): The user's first name.
last_name (str, optional): The user's last name.
role (str, required): The user's role (Admin, Developer, ... established at registration).
group (str, optional): The user's group (laboratory name, company name, ...).
email (str, required): The user's email address.
age (int, optional): The user's age.
weight (float, optional): The user's weight.
gender (str, optional): The user's gender.
Returns:
str: The user ID if the user is successfully created/retrieved, None otherwise.
Example:
user_id = set_user(first_name="John", last_name="Doe", email="john.doe@example.com", age=30, weight=70.5, gender="X")
if user_id:
print(f"User created/retrieved with ID: {user_id}")
else:
print("User creation failed.")
**NOTE**: In order to use this function, your role should be `Admin`
315def search_user_by_mail(user_id, email): 316 """ 317 Search for a user by email. 318 319 This function sends a GET request to the server to search for a user by the provided email address. 320 321 Args: 322 **user_id** (*str*): The user id (obtained through free registration with HABS) 323 **email** (*str*): The email address of the user to search for. 324 325 Returns: 326 *str*: The user ID if the user is found, None otherwise. 327 328 Example: 329 ``` 330 user_id = search_user_by_mail("john.doe@example.com") 331 if user_id: 332 print(f"User found with ID: {user_id}") 333 else: 334 print("User not found.") 335 ``` 336 """ 337 url = f"{BASE_URL}/api/{VERSION}/users?email={email}" 338 339 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 340 341 if response.status_code == 200: 342 user_id = response.json().get('user_id') 343 print("User found:", user_id) 344 return user_id 345 else: 346 print("User not found.", response.text) 347 return None
Search for a user by email.
This function sends a GET request to the server to search for a user by the provided email address.
Args:
user_id (str): The user id (obtained through free registration with HABS)
email (str): The email address of the user to search for.
Returns:
str: The user ID if the user is found, None otherwise.
Example:
user_id = search_user_by_mail("john.doe@example.com")
if user_id:
print(f"User found with ID: {user_id}")
else:
print("User not found.")
351def get_user_by_id(user_id): 352 """ 353 Retrieve user data by user ID. 354 355 This function sends a GET request to the server to retrieve user data for the specified user ID. 356 The response data is decrypted using AES before returning the user data. 357 358 Args: 359 **user_id** (*str*): The unique identifier of the user to retrieve. 360 361 Returns: 362 *dict*: The user data if the user is found, None otherwise. 363 364 Example: 365 ``` 366 user_data = get_user_by_id("1234567890") 367 if user_data: 368 print(f"User data: {user_data}") 369 else: 370 print("User not found.") 371 ``` 372 """ 373 url = f"{BASE_URL}/api/{VERSION}/users/{user_id}" 374 375 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 376 377 if response.status_code == 200: 378 print("User found.") 379 encrypted_data = response.content 380 aes_key_b64 = os.environ.get('AES_KEY') 381 aes_key_bytes = base64.b64decode(aes_key_b64) 382 decrypted_json_string = decrypt_message(encrypted_data, aes_key_bytes) 383 user_data = json.loads(decrypted_json_string)['user_data'] 384 return user_data 385 else: 386 print("User not found:", response.text) 387 return None
Retrieve user data by user ID.
This function sends a GET request to the server to retrieve user data for the specified user ID.
The response data is decrypted using AES before returning the user data.
Args:
user_id (str): The unique identifier of the user to retrieve.
Returns:
dict: The user data if the user is found, None otherwise.
Example:
user_data = get_user_by_id("1234567890")
if user_data:
print(f"User data: {user_data}")
else:
print("User not found.")
391def set_session(metadata, user_id): 392 """ 393 Create a new simple session. 394 395 This function sends a POST request to the server to create a new simple session using the provided metadata. 396 The metadata is encrypted using AES before being sent to the server. 397 398 Args: 399 **metadata** (*dict*): A dictionary containing the session metadata. The only required metadata for the simple session are the user_id and a date. 400 **user_id** (*str*): The user id (obtained through free registration with HABS) 401 402 Returns: 403 *str*: The unique identifier of the created session if successful, None otherwise. 404 405 Example: 406 ``` 407 session_metadata = { 408 "user_id": "1076203852085", 409 "session_date": "2024-05-30T12:00:00Z" 410 } 411 session_id = set_session(session_metadata) 412 if session_id: 413 print(f"Session created with ID: {session_id}") 414 else: 415 print("Failed to create session.") 416 ``` 417 """ 418 url = f"{BASE_URL}/api/{VERSION}/sessions" 419 _session = metadata 420 _session = json.dumps(_session).encode('utf-8') 421 aes_key_b64 = os.environ.get('AES_KEY') 422 aes_key_bytes = base64.b64decode(aes_key_b64) 423 response = requests.post( 424 url, 425 data=encrypt_message(_session, aes_key_bytes), 426 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 427 ) 428 429 if response.status_code == 200: 430 print("Session successfully created.") 431 # Extract the unique identifier for the uploaded data 432 session_id = response.json().get('session_id') 433 434 # print("session_id: ",session_id) 435 return session_id 436 else: 437 print("Session failed:", response.text) 438 return None
Create a new simple session.
This function sends a POST request to the server to create a new simple session using the provided metadata. The metadata is encrypted using AES before being sent to the server.
Args:
metadata (dict): A dictionary containing the session metadata. The only required metadata for the simple session are the user_id and a date.
user_id (str): The user id (obtained through free registration with HABS)
Returns:
str: The unique identifier of the created session if successful, None otherwise.
Example:
session_metadata = {
"user_id": "1076203852085",
"session_date": "2024-05-30T12:00:00Z"
}
session_id = set_session(session_metadata)
if session_id:
print(f"Session created with ID: {session_id}")
else:
print("Failed to create session.")
442def get_data_by_id(data_id, user_id): 443 """ 444 Retrieve raw data by its unique identifier from the server. 445 446 This function sends a GET request to fetch raw data associated with a specific identifier. It 447 assumes that the data, if retrieved successfully, does not require decryption and is directly accessible. 448 449 Args: 450 **data_id** (*str*): The unique identifier for the data to be retrieved. 451 **user_id** (*str*): The user id (obtained through free registration with HABS) 452 453 Returns: 454 **dict**: The raw data if retrieval is successful, None otherwise. 455 456 Example: 457 ``` 458 data_id = "1234" 459 raw_data = get_data_by_id(data_id) 460 ... use the data 461 ``` 462 """ 463 url = f"{BASE_URL}/api/{VERSION}/rawdata/{data_id}" 464 465 # response = requests.get(url) 466 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 467 468 if response.status_code == 200: 469 print("Retrieved data successfully.") 470 # decrypt 471 return response.json().get('rawData') 472 else: 473 print("Failed to retrieve data:", response.text)
Retrieve raw data by its unique identifier from the server.
This function sends a GET request to fetch raw data associated with a specific identifier. It assumes that the data, if retrieved successfully, does not require decryption and is directly accessible.
Args:
data_id (str): The unique identifier for the data to be retrieved.
user_id (str): The user id (obtained through free registration with HABS)
Returns:
dict: The raw data if retrieval is successful, None otherwise.
Example:
data_id = "1234"
raw_data = get_data_by_id(data_id)
... use the data
478def find_sessions_by_user(user_id): 479 """ 480 Retrieve session IDs associated with a given user. 481 482 This function sends a GET request to the API to retrieve all session IDs for a specified user. 483 It expects the user ID to be passed as an argument and uses the user ID for authentication. 484 485 Args: 486 user_id (str): The user ID (obtained through free registration with HABS). 487 488 Returns: 489 list: A list of session IDs if the request is successful. 490 491 Raises: 492 Exception: If the request fails or if there is an error in the response. 493 494 Example: 495 >>> sessions = find_sessions_by_user("12345") 496 >>> print(sessions) 497 ["session1", "session2", "session3"] 498 499 Notes: 500 Ensure that the environment variable `AES_KEY` is set to the base64 encoded AES key. 501 """ 502 url = f"{BASE_URL}/api/{VERSION}/sessions/{user_id}" 503 504 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 505 506 if response.status_code == 200: 507 print("User found.") 508 encrypted_data = response.content 509 aes_key_b64 = os.environ.get('AES_KEY') 510 aes_key_bytes = base64.b64decode(aes_key_b64) 511 decrypted_json_string = decrypt_message(encrypted_data, aes_key_bytes) 512 session_ids = json.loads(decrypted_json_string)['session_ids'] 513 return session_ids 514 else: 515 print("Failed to retrieve data:", response.text)
Retrieve session IDs associated with a given user.
This function sends a GET request to the API to retrieve all session IDs for a specified user. It expects the user ID to be passed as an argument and uses the user ID for authentication.
Args: user_id (str): The user ID (obtained through free registration with HABS).
Returns: list: A list of session IDs if the request is successful.
Raises: Exception: If the request fails or if there is an error in the response.
Example:
sessions = find_sessions_by_user("12345") print(sessions) ["session1", "session2", "session3"]
Notes:
Ensure that the environment variable AES_KEY
is set to the base64 encoded AES key.
520def get_data_by_session(session_id, user_id): 521 """ 522 Retrieve raw data associated with a specific session identifier from the server. 523 524 This function sends a GET request to fetch all raw data linked to the given session ID. The data 525 is returned in its raw form assuming it does not require decryption for usage. 526 527 Args: 528 **session_id** (*str*): The session identifier whose associated data is to be retrieved. 529 **user_id** (*str*): The user id (obtained through free registration with HABS) 530 531 Returns: 532 *dict*: The raw data linked to the session if retrieval is successful, None otherwise. 533 534 Example: 535 ``` 536 session_id = "abcd1234" 537 session_data = get_data_by_session(session_id) 538 if session_data: 539 print("Data retrieved:", session_data) 540 else: 541 print("Failed to retrieve data.") 542 ``` 543 """ 544 url = f"{BASE_URL}/api/{VERSION}/sessions/{session_id}/rawdata" 545 546 # response = requests.get(url) 547 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 548 549 if response.status_code == 200: 550 print("Retrieved data successfully.") 551 # decrypt 552 return response.json().get('data') 553 else: 554 print("Failed to retrieve data:", response.text)
Retrieve raw data associated with a specific session identifier from the server.
This function sends a GET request to fetch all raw data linked to the given session ID. The data is returned in its raw form assuming it does not require decryption for usage.
Args:
session_id (str): The session identifier whose associated data is to be retrieved.
user_id (str): The user id (obtained through free registration with HABS)
Returns:
dict: The raw data linked to the session if retrieval is successful, None otherwise.
Example:
session_id = "abcd1234"
session_data = get_data_by_session(session_id)
if session_data:
print("Data retrieved:", session_data)
else:
print("Failed to retrieve data.")
559def get_data_ids_by_session(session_id, user_id): 560 """ 561 Retrieve a list of data IDs associated with a specific session from the server. 562 563 This function sends a GET request to fetch the IDs of all data entries linked to a specified session ID. 564 The IDs are returned as a list. The function assumes the data does not require decryption for usage. 565 566 Args: 567 **session_id** (*str*): The session identifier for which data IDs are to be retrieved. 568 **user_id** (*str*): The user id (obtained through free registration with HABS) 569 570 Returns: 571 *list*: A list of data IDs if retrieval is successful, None otherwise. 572 573 Example: 574 ``` 575 session_id = "abcd1234" 576 data_ids = get_data_ids_by_session(session_id) 577 if data_ids: 578 print("Data IDs retrieved:", data_ids) 579 else: 580 print("Failed to retrieve data IDs.") 581 ``` 582 """ 583 url = f"{BASE_URL}/api/{VERSION}/sessions/{session_id}/ids" 584 585 # response = requests.get(url) 586 response = requests.get(url, headers={'X-User-ID':user_id}) # mongo _id for the user document. Communicated at user creation. 587 588 if response.status_code == 200: 589 print("Retrieved ids successfully.") 590 # decrypt 591 return response.json().get('ids') 592 else: 593 print("Failed to retrieve ids:", response.text)
Retrieve a list of data IDs associated with a specific session from the server.
This function sends a GET request to fetch the IDs of all data entries linked to a specified session ID. The IDs are returned as a list. The function assumes the data does not require decryption for usage.
Args:
session_id (str): The session identifier for which data IDs are to be retrieved.
user_id (str): The user id (obtained through free registration with HABS)
Returns:
list: A list of data IDs if retrieval is successful, None otherwise.
Example:
session_id = "abcd1234"
data_ids = get_data_ids_by_session(session_id)
if data_ids:
print("Data IDs retrieved:", data_ids)
else:
print("Failed to retrieve data IDs.")
598def upload_data(metadata, timestamps, user_id, data, ppg_red, ppg_ir): 599 """ 600 Uploads EEG (and PPG) data to the server along with associated metadata. 601 602 This function compiles different types of physiological data along with metadata into a single dictionary, 603 encrypts the data, and then uploads it via a POST request. Upon successful upload, the server returns a 604 unique identifier for the data which can then be used for future queries or operations. 605 606 Args: 607 **metadata** (*dict*): Information about the data such as subject details and session parameters. 608 **timestamps** (*list*): List of timestamps correlating with each data point. 609 **user_id** (*str*): The user id (obtained through free registration with HABS) 610 **data** (*list*): EEG data points. 611 **ppg_red** (*list*): Red photoplethysmogram data points. 612 **ppg_ir** (*list*): Infrared photoplethysmogram data points. 613 614 Returns: 615 *tuple*: A tuple containing the data ID of the uploaded data if successful, and None otherwise. 616 617 Notes: 618 Ensure that timestamps has the same length of data last dimension. 619 620 Example: 621 ``` 622 metadata = {"session_id": "1234", "subject_id": "001"} 623 timestamps = [1597709184, 1597709185] 624 data = [0.1, 0.2] 625 ppg_red = [123, 124] 626 ppg_ir = [125, 126] 627 data_id, error = upload_data(metadata, timestamps, data, ppg_red, ppg_ir) 628 if data_id: 629 print("Data uploaded successfully. Data ID:", data_id) 630 else: 631 print("Upload failed with error:", error) 632 ``` 633 """ 634 url = f"{BASE_URL}/api/{VERSION}/rawdata" 635 _data = { 636 "metadata": metadata, 637 "timestamps": timestamps, 638 "data": data, 639 "ppg_red": ppg_red, 640 "ppg_ir": ppg_ir 641 } 642 _data = json.dumps(_data).encode('utf-8') 643 644 # response = requests.post(url, json=_data) 645 aes_key_b64 = os.environ.get('AES_KEY') 646 aes_key_bytes = base64.b64decode(aes_key_b64) 647 response = requests.post( 648 url, 649 data=encrypt_message(_data, aes_key_bytes), 650 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 651 ) 652 # response = requests.get(url, headers={'X-User-ID':USERID}) # mongo _id for the user document. Communicated at user creation. 653 654 if response.status_code == 200: 655 print('.', end='', flush=True) 656 # Extract the unique identifier for the uploaded data 657 data_id = response.json().get('data_id') 658 return data_id, None 659 else: 660 print("Upload failed:", response.text) 661 return None
Uploads EEG (and PPG) data to the server along with associated metadata.
This function compiles different types of physiological data along with metadata into a single dictionary, encrypts the data, and then uploads it via a POST request. Upon successful upload, the server returns a unique identifier for the data which can then be used for future queries or operations.
Args:
metadata (dict): Information about the data such as subject details and session parameters.
timestamps (list): List of timestamps correlating with each data point.
user_id (str): The user id (obtained through free registration with HABS)
data (list): EEG data points.
ppg_red (list): Red photoplethysmogram data points.
ppg_ir (list): Infrared photoplethysmogram data points.
Returns:
tuple: A tuple containing the data ID of the uploaded data if successful, and None otherwise.
Notes: Ensure that timestamps has the same length of data last dimension.
Example:
metadata = {"session_id": "1234", "subject_id": "001"}
timestamps = [1597709184, 1597709185]
data = [0.1, 0.2]
ppg_red = [123, 124]
ppg_ir = [125, 126]
data_id, error = upload_data(metadata, timestamps, data, ppg_red, ppg_ir)
if data_id:
print("Data uploaded successfully. Data ID:", data_id)
else:
print("Upload failed with error:", error)
666def acquire_send_raw(user_id, date, board, serial_number, stream_duration, buffer_duration, session_type="", tags=[], callback=None, extra=None): 667 """ 668 Asynchronously acquires raw data from a specific EEG board and sends it to the server. 669 670 This function connects to an EEG board, initiates a data acquisition session, and sends the collected data 671 to the server in real-time or near real-time. It ensures that all the data handled during the session 672 is associated with a unique session ID and metadata that includes user and session details. The function 673 will validate the session metadata before proceeding with data acquisition and sending. 674 675 Args: 676 **user_id** (*str*): The unique identifier of the user for whom the data is being collected. 677 **date** (*str*): The date of the session, used for metadata purposes. 678 **board** (*int*): Identifier for the EEG board from which data will be acquired. 679 **stream_duration** (*int*): Duration in seconds for which data will be streamed from the board. 680 **buffer_duration** (*int*): Time in seconds for how often the data is buffered and sent. 681 682 Returns: 683 *str* or *bool*: The session ID if the operation is successful; False otherwise. 684 685 Raises: 686 **ConnectionError**: If the board connection fails. 687 **ValidationError**: If the metadata does not comply with the required schema. 688 689 Example: 690 ``` 691 session = acquire_send_raw('user123', '2021-06-01', 'MUSE_S', 300, 10) 692 if session: 693 print(f"Session successfully started with ID: {session}") 694 else: 695 print("Failed to start session") 696 ``` 697 """ 698 # set session for the data 699 # We set a session id for the current interaction with the API (even if we fail to get the board, it will be important to store the failure) 700 session_metadata = { 701 "user_id": user_id, # add user to the session for reference 702 "session_date": date, 703 "session_type": session_type, 704 "session_tags": tags 705 } 706 session_id = set_session(metadata={**session_metadata}, user_id=user_id) 707 print("\nSession initialized. You can visualize it here:\n ", "https://habs.ai/live.html?session_id="+str(session_id), "\n") 708 709 if validate_metadata(session_metadata, "sessionSchema"): 710 asyncio.run( 711 _acquire_send_raw(user_id, session_id, board, serial_number, stream_duration, buffer_duration, callback, extra) 712 ) 713 return session_id 714 else: 715 print("Session initialization failed.") 716 return False
Asynchronously acquires raw data from a specific EEG board and sends it to the server.
This function connects to an EEG board, initiates a data acquisition session, and sends the collected data to the server in real-time or near real-time. It ensures that all the data handled during the session is associated with a unique session ID and metadata that includes user and session details. The function will validate the session metadata before proceeding with data acquisition and sending.
Args:
user_id (str): The unique identifier of the user for whom the data is being collected.
date (str): The date of the session, used for metadata purposes.
board (int): Identifier for the EEG board from which data will be acquired.
stream_duration (int): Duration in seconds for which data will be streamed from the board.
buffer_duration (int): Time in seconds for how often the data is buffered and sent.
Returns:
str or bool: The session ID if the operation is successful; False otherwise.
Raises:
ConnectionError: If the board connection fails.
ValidationError: If the metadata does not comply with the required schema.
Example:
session = acquire_send_raw('user123', '2021-06-01', 'MUSE_S', 300, 10)
if session:
print(f"Session successfully started with ID: {session}")
else:
print("Failed to start session")
738def send_file(user_id, date, edf_file, ch_nrs=None, ch_names=None, session_type="", tags=[]): 739 """ 740 Uploads EEG data from a file to the server along with associated metadata. 741 742 This function compiles EEG data from an [EDF file](https://www.edfplus.info/downloads/index.html) along with metadata into a single dictionary, 743 encrypts the data, and then uploads it via a POST request. Upon successful upload, the server returns a 744 unique identifier for the session which can then be used for future queries or operations. 745 746 Args: 747 **user_id** (*str*): The unique identifier of the user for whom the data is being collected. 748 **metadata** (*dict*): Information about the data such as subject details and session parameters. 749 **date** (*str*): The date of the session, used for metadata purposes. 750 **edf_file** (*str*): name of an EDF file. 751 **ch_nrs** (*list of int*, optional): The indices of the channels to read. The default is None. 752 **ch_names** (*list of str*, optional): The names of channels to read. The default is None. 753 754 Returns: 755 *tuple*: A tuple containing the session ID of the uploaded data if successful, and None otherwise. 756 757 Example: 758 ``` 759 session = send_file('user123', '2021-06-01', 'nameoffile.edf') 760 if session: 761 print(f"Session successfully started with ID: {session}") 762 else: 763 print("Failed to start session") 764 ``` 765 """ 766 767 try: 768 signals, signal_headers, header = highlevel.read_edf(edf_file, ch_nrs, ch_names) 769 770 max_time = signals.shape[1] / signal_headers[0]['sample_frequency'] 771 timestamps = np.linspace(header['startdate'].timestamp(), max_time, signals.shape[1]) 772 773 session_metadata = { 774 "user_id": user_id, # add user to the session for reference 775 "session_date": header['startdate'].strftime("%m/%d/%Y, %H:%M:%S"), 776 "session_type": session_type, 777 "session_tags": tags 778 } 779 if validate_metadata(session_metadata, "sessionSchema"): 780 session_id = set_session(metadata={**session_metadata}, user_id=user_id) 781 metadata = {'session_id':session_id, **session_metadata, **convert_datetime_in_dict(header), **convert_datetime_in_dict(signal_headers[0])} 782 783 chunks = ((signals.size * signals.itemsize)//300000)+1 784 timestamps_chunks = np.array_split(timestamps, chunks) 785 signals_chunks = np.array_split(signals, chunks, axis=1) 786 json_data = json.dumps(signals_chunks[0].tolist()) 787 size_in_bytes = sys.getsizeof(json_data) 788 print("%d total bytes will be sent into %d chunks of %d bytes" % (signals.size * signals.itemsize, chunks, size_in_bytes)) 789 790 for timestamps_chunk,signals_chunk in zip(timestamps_chunks, signals_chunks): 791 upload_data(metadata, timestamps_chunk.tolist(), user_id, signals_chunk.tolist(), [], []) 792 793 return session_id 794 else: 795 return False 796 except Exception as e: 797 print("A general error occurred:", e) 798 return False
Uploads EEG data from a file to the server along with associated metadata.
This function compiles EEG data from an EDF file along with metadata into a single dictionary, encrypts the data, and then uploads it via a POST request. Upon successful upload, the server returns a unique identifier for the session which can then be used for future queries or operations.
Args:
user_id (str): The unique identifier of the user for whom the data is being collected.
metadata (dict): Information about the data such as subject details and session parameters.
date (str): The date of the session, used for metadata purposes.
edf_file (str): name of an EDF file.
ch_nrs (list of int, optional): The indices of the channels to read. The default is None.
ch_names (list of str, optional): The names of channels to read. The default is None.
Returns:
tuple: A tuple containing the session ID of the uploaded data if successful, and None otherwise.
Example:
session = send_file('user123', '2021-06-01', 'nameoffile.edf')
if session:
print(f"Session successfully started with ID: {session}")
else:
print("Failed to start session")
804def set_pipe(metadata, pipeline, params, user_id): 805 """ 806 Configures and initiates a data processing pipeline for a session on the server. 807 808 This function sends metadata and processing parameters to a specified pipeline endpoint 809 to create a data processing session. It encrypts the session data before sending to ensure 810 security. The function checks the server response to confirm the session creation. 811 812 Args: 813 **metadata** (*dict*): A dictionary containing metadata about the session, typically including 814 details such as user ID and session date. 815 **pipeline** (*str*): The identifier for the processing pipeline to be used. 816 **params** (*dict*): Parameters specific to the processing pipeline, detailing how data should 817 be processed. 818 **user_id** (*str*): The user id (obtained through free registration with HABS) 819 820 Returns: 821 *str* or *None*: The session ID if the session is successfully created, or None if the operation fails. 822 823 Raises: 824 **requests.exceptions.RequestException**: An error from the Requests library when an HTTP request fails. 825 **KeyError**: If necessary keys are missing in the environment variables. 826 827 Example: 828 ``` 829 session_metadata = {"user_id": "123", "session_date": "2024-06-03"} 830 processing_params = {"filter_type": "lowpass", "cutoff_freq": 30} 831 session_id = set_pipe(session_metadata, 'eeg_smoothing', processing_params) 832 if session_id: 833 print(f"Pipeline session created with ID: {session_id}") 834 else: 835 print("Failed to create pipeline session") 836 ``` 837 """ 838 url = f"{BASE_URL}/api/{VERSION}/sessions/pipe/{pipeline}" 839 _session = { 840 "metadata": metadata, 841 "processing_params": params, 842 } 843 _session = json.dumps(_session).encode('utf-8') 844 aes_key_b64 = os.environ.get('AES_KEY') 845 aes_key_bytes = base64.b64decode(aes_key_b64) 846 response = requests.post( 847 url, 848 data=encrypt_message(_session, aes_key_bytes), 849 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 850 ) 851 if response.status_code == 200: 852 print("Session successfully created.") 853 # Extract the unique identifier for the uploaded data 854 session_id = response.json().get('session_id') 855 # print(session_id) 856 return session_id 857 else: 858 print("Session failed:", response.text) 859 return None
Configures and initiates a data processing pipeline for a session on the server.
This function sends metadata and processing parameters to a specified pipeline endpoint to create a data processing session. It encrypts the session data before sending to ensure security. The function checks the server response to confirm the session creation.
Args:
metadata (dict): A dictionary containing metadata about the session, typically including
details such as user ID and session date.
pipeline (str): The identifier for the processing pipeline to be used.
params (dict): Parameters specific to the processing pipeline, detailing how data should
be processed.
user_id (str): The user id (obtained through free registration with HABS)
Returns:
str or None: The session ID if the session is successfully created, or None if the operation fails.
Raises:
requests.exceptions.RequestException: An error from the Requests library when an HTTP request fails.
KeyError: If necessary keys are missing in the environment variables.
Example:
session_metadata = {"user_id": "123", "session_date": "2024-06-03"}
processing_params = {"filter_type": "lowpass", "cutoff_freq": 30}
session_id = set_pipe(session_metadata, 'eeg_smoothing', processing_params)
if session_id:
print(f"Pipeline session created with ID: {session_id}")
else:
print("Failed to create pipeline session")
864def upload_pipedata(metadata, timestamps, user_id, data, ppg_red, ppg_ir): 865 """ 866 Uploads processed data to a specific session on the server. 867 868 This function is responsible for uploading various data streams associated with a session, including 869 timestamps and physiological measurements such as PPG (Photoplethysmogram). The data is encrypted before 870 sending to ensure confidentiality and integrity. 871 872 Args: 873 **metadata** (*dict*): Contains session-related metadata including the session ID. 874 **timestamps** (*list*): A list of timestamps corresponding to each data point. 875 **user_id** (*str*): The user id (obtained through free registration with HABS) 876 **data** (*list*): The main data collected, e.g., EEG readings. 877 **ppg_red** (*list*): Red channel data from a PPG sensor. 878 **ppg_ir** (*list*): Infrared channel data from a PPG sensor. 879 880 Returns: 881 *tuple*: A tuple containing the data ID if the upload is successful and the processed data, or None if the upload fails. 882 883 Raises: 884 **requests.exceptions.RequestException: An error from the Requests library when an HTTP request fails. 885 **KeyError**: If necessary keys are missing in the environment variables. 886 887 Example: 888 ``` 889 session_metadata = {"session_id": "12345"} 890 timestamps = [1597709165, 1597709166, ...] 891 data = [0.1, 0.2, ...] 892 ppg_red = [12, 15, ...] 893 ppg_ir = [20, 22, ...] 894 data_id, processed_data = upload_pipedata(session_metadata, timestamps, data, ppg_red, ppg_ir) 895 if data_id: 896 print(f"Data uploaded successfully with ID: {data_id}") 897 else: 898 print("Failed to upload data") 899 ``` 900 """ 901 url = f"{BASE_URL}/api/{VERSION}/pipedata/{metadata['session_id']}" # the metadata contain session_id to consistently pass it with each upload 902 903 _data = { 904 "metadata": metadata, 905 "timestamps": timestamps, 906 "data": data, 907 "ppg_red": ppg_red, 908 "ppg_ir": ppg_ir 909 } 910 _data = json.dumps(_data).encode('utf-8') 911 aes_key_b64 = os.environ.get('AES_KEY') 912 aes_key_bytes = base64.b64decode(aes_key_b64) 913 response = requests.post( 914 url, 915 data=encrypt_message(_data, aes_key_bytes), 916 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 917 ) 918 919 if response.status_code == 200: 920 print('.', end='', flush=True) 921 # Extract the unique identifier for the uploaded data 922 data_id = response.json().get('data_id') 923 # Retrieve the processed data 924 data = response.json().get('pipeData') 925 return data_id, data 926 else: 927 print("Upload failed:", response.text) 928 return None
Uploads processed data to a specific session on the server.
This function is responsible for uploading various data streams associated with a session, including timestamps and physiological measurements such as PPG (Photoplethysmogram). The data is encrypted before sending to ensure confidentiality and integrity.
Args:
metadata (dict): Contains session-related metadata including the session ID.
timestamps (list): A list of timestamps corresponding to each data point.
user_id (str): The user id (obtained through free registration with HABS)
data (list): The main data collected, e.g., EEG readings.
ppg_red (list): Red channel data from a PPG sensor.
ppg_ir (list): Infrared channel data from a PPG sensor.
Returns:
tuple: A tuple containing the data ID if the upload is successful and the processed data, or None if the upload fails.
Raises:
requests.exceptions.RequestException: An error from the Requests library when an HTTP request fails.
**KeyError: If necessary keys are missing in the environment variables.
Example:
session_metadata = {"session_id": "12345"}
timestamps = [1597709165, 1597709166, ...]
data = [0.1, 0.2, ...]
ppg_red = [12, 15, ...]
ppg_ir = [20, 22, ...]
data_id, processed_data = upload_pipedata(session_metadata, timestamps, data, ppg_red, ppg_ir)
if data_id:
print(f"Data uploaded successfully with ID: {data_id}")
else:
print("Failed to upload data")
932def acquire_send_pipe(pipeline, params, user_id, date, board, serial_number, stream_duration, buffer_duration, session_type="", tags=[], callback=None, extra=None): 933 """ 934 Acquires data from a board, processes it according to a specified pipeline, and sends it to a server. 935 This function handles setting up a session for data acquisition and processing, connects to a board, 936 and manages the data flow from acquisition through processing to uploading. It uses an asynchronous loop 937 to handle the operations efficiently, suitable for real-time data processing scenarios. 938 939 Args: 940 **pipeline** (*str*): Name of the processing pipeline to use. 941 **params** (*dict*): Parameters for the pipeline processing. 942 **user_id** (*str*): The user ID to which the session will be associated. 943 **date** (*str*): Date of the session for tracking purposes. 944 **board** (*int*): Identifier for the hardware board to use for data acquisition. 945 **stream_duration** (*int*): Duration in seconds to stream data from the board. 946 **buffer_duration** (*int*): Duration in seconds to buffer data before processing. 947 **callback** (*function*): Optional callback function to execute after data is sent. 948 949 Returns: 950 *str* or *bool*: The session ID if successful, False otherwise. 951 952 """ 953 # set session for the data 954 # We set a session id for the current interaction with the API (even if we fail to get the board, it will be important to store the failure) 955 session_metadata = { 956 "user_id": user_id, # add user to the session for reference 957 "session_date": date, 958 "session_type": session_type, 959 "session_tags": tags 960 } 961 if validate_metadata(session_metadata, "sessionSchema"): 962 session_id = set_pipe(metadata={**session_metadata}, pipeline=pipeline, params=params, user_id=user_id) 963 print("\nSession initialized. You can visualize it here:\n ", "https://habs.ai/bos/live.html?session_id="+str(session_id), "\n") 964 965 asyncio.run( 966 _acquire_send_pipe(pipeline, params, user_id, session_id, board, serial_number, stream_duration, buffer_duration, callback, extra) 967 ) 968 return session_id #, self.processed_data 969 else: 970 print("Session initialization failed.") 971 return False
Acquires data from a board, processes it according to a specified pipeline, and sends it to a server. This function handles setting up a session for data acquisition and processing, connects to a board, and manages the data flow from acquisition through processing to uploading. It uses an asynchronous loop to handle the operations efficiently, suitable for real-time data processing scenarios.
Args:
pipeline (str): Name of the processing pipeline to use.
params (dict): Parameters for the pipeline processing.
user_id (str): The user ID to which the session will be associated.
date (str): Date of the session for tracking purposes.
board (int): Identifier for the hardware board to use for data acquisition.
stream_duration (int): Duration in seconds to stream data from the board.
buffer_duration (int): Duration in seconds to buffer data before processing.
callback (function): Optional callback function to execute after data is sent.
Returns:
str or bool: The session ID if successful, False otherwise.
994def get_user_database(user_id): 995 """ 996 Retrieve all user data by user ID. 997 998 This function sends a GET request to the server to dump all data stored for the specified user ID. 999 The response data is decrypted using AES before returning the user data. 1000 1001 Args: 1002 **user_id** (*str*): The unique identifier of the user to retrieve. 1003 1004 Returns: 1005 *None*: A zip file contaning all data as JSON files, None otherwise. 1006 1007 Example: 1008 ``` 1009 user_data = get_user_database("1234567890") 1010 if user_data: 1011 print(f"User data: {user_data}") 1012 else: 1013 print("User not found.") 1014 ``` 1015 """ 1016 url = f"{BASE_URL}/api/{VERSION}/database/dump/{user_id}" 1017 1018 response = requests.get(url, headers={'X-User-ID': user_id}, stream=True) 1019 1020 if response.status_code == 200: 1021 # Open a local file with write-binary mode 1022 strtime = datetime.today().strftime("%Y%m%d_%H%M%S") 1023 1024 with open(f"brainos_{strtime}_dump.zip", 'wb') as file: 1025 # Write the response content to the file in chunks 1026 for chunk in response.iter_content(chunk_size=8192): 1027 file.write(chunk) 1028 print("Database dump saved successfully.") 1029 return True 1030 else: 1031 print("User not found:", response.text) 1032 return None
Retrieve all user data by user ID.
This function sends a GET request to the server to dump all data stored for the specified user ID.
The response data is decrypted using AES before returning the user data.
Args:
user_id (str): The unique identifier of the user to retrieve.
Returns:
None: A zip file contaning all data as JSON files, None otherwise.
Example:
user_data = get_user_database("1234567890")
if user_data:
print(f"User data: {user_data}")
else:
print("User not found.")
1038def create_tagged_interval(user_id, session_id, eeg_data_id, start_time, end_time, tags, channel_ids=None): 1039 """ 1040 Creates a tagged interval by sending the interval data to the server. 1041 1042 This function performs the following steps: 1043 1. Constructs the interval data dictionary. 1044 2. Validates the interval data against the "tagSchema". 1045 3. Sends the interval data to the server. 1046 1047 Args: 1048 **session_id** (*str*): The session id. 1049 **eeg_data_id** (*str*): The EEG data id. 1050 **start_time** (*str*): The start time of the interval in ISO 8601 format. 1051 **end_time** (*str*): The end time of the interval in ISO 8601 format. 1052 **tags** (*list*): List of tags, each tag is a dictionary containing a "tag" and "properties". 1053 **channel_ids** (*list*, optional): List of channel ids the tag applies to. If None, applies to all channels. 1054 1055 Returns: 1056 *str*: The interval ID if the interval is successfully created, None otherwise. 1057 1058 Example: 1059 ``` 1060 interval_id = create_tagged_interval( 1061 session_id="session_123", 1062 eeg_data_id="eeg_data_456", 1063 start_time="2023-01-01T00:00:00Z", 1064 end_time="2023-01-01T00:05:00Z", 1065 tags=[{"tag": "seizure", "properties": {"severity": "high"}}] 1066 ) 1067 if interval_id: 1068 print(f"Tagged interval created with ID: {interval_id}") 1069 else: 1070 print("Tagged interval creation failed.") 1071 ``` 1072 """ 1073 url = f"{BASE_URL}/api/{VERSION}/session/{session_id}/tag" 1074 interval_data = { 1075 "user_id": user_id, 1076 "session_id": session_id, 1077 "eeg_data_id": eeg_data_id, 1078 "start_time": start_time, 1079 "end_time": end_time, 1080 "tags": tags, 1081 "channel_ids": channel_ids if channel_ids else [] 1082 } 1083 1084 if validate_metadata(interval_data, "tagSchema"): 1085 response = requests.post( 1086 url, 1087 json=interval_data, 1088 headers={'Content-Type': 'application/json'} 1089 ) 1090 1091 if response.status_code == 201: 1092 print("Tagged interval successfully created.") 1093 interval_id = response.json().get('interval_id') 1094 return interval_id 1095 else: 1096 print("Tagged interval creation failed:", response.text) 1097 return None 1098 else: 1099 print("Tagged interval creation failed due to validation error.")
Creates a tagged interval by sending the interval data to the server.
This function performs the following steps:
- Constructs the interval data dictionary.
- Validates the interval data against the "tagSchema".
- Sends the interval data to the server.
Args: session_id (str): The session id. eeg_data_id (str): The EEG data id. start_time (str): The start time of the interval in ISO 8601 format. end_time (str): The end time of the interval in ISO 8601 format. tags (list): List of tags, each tag is a dictionary containing a "tag" and "properties". channel_ids (list, optional): List of channel ids the tag applies to. If None, applies to all channels.
Returns: str: The interval ID if the interval is successfully created, None otherwise.
Example:
interval_id = create_tagged_interval(
session_id="session_123",
eeg_data_id="eeg_data_456",
start_time="2023-01-01T00:00:00Z",
end_time="2023-01-01T00:05:00Z",
tags=[{"tag": "seizure", "properties": {"severity": "high"}}]
)
if interval_id:
print(f"Tagged interval created with ID: {interval_id}")
else:
print("Tagged interval creation failed.")
1106def process_session_pipe(pipeline, params, user_id, date, existing_session_id, session_type="", tags=[]): 1107 """ 1108 Process a session pipeline with specified parameters and metadata. 1109 1110 This function processes an existing session by applying a specified pipeline and parameters. 1111 It sends a POST request to the API with the session metadata and processing parameters, 1112 creating a new session based on the existing one. 1113 1114 Args: 1115 **pipeline** (*str*): The pipeline to be applied to the session. 1116 **params** (*dict*): The processing parameters for the pipeline. 1117 **user_id** (*str*): The user ID (obtained through free registration with HABS). 1118 **date** (*str*): The date of the session. 1119 **existing_session_id** (*str*): The ID of the existing session to be processed. 1120 **session_type** (*str*, optional): The type of the new session. Defaults to an empty string. 1121 **tags** (*list*, optional): A list of tags associated with the session. Defaults to an empty list. 1122 1123 Returns: 1124 tuple: A tuple containing the new session ID and the processed data if the request is successful. 1125 None: If the session creation fails. 1126 bool: False if the session metadata is invalid. 1127 1128 Example: 1129 >>> new_session_id, processed_data = process_session_pipe("my_pipeline", {"param1": "value1"}, "12345", "2023-07-03", "existing_session_001") 1130 >>> print(new_session_id, processed_data) 1131 1132 Notes: 1133 Ensure that the environment variable `AES_KEY` is set to the base64 encoded AES key. 1134 1135 Raises: 1136 Exception: If there is an error in the request or response. 1137 1138 """ 1139 session_metadata = { 1140 "user_id": user_id, # add user to the session for reference 1141 "session_date": date, # .strftime("%m/%d/%Y, %H:%M:%S"), 1142 "existing_session_id": existing_session_id, 1143 "session_type": f"[On {existing_session_id}]: {session_type}", # type of the new session 1144 "session_tags": tags 1145 } 1146 if validate_metadata(session_metadata, "sessionSchema"): 1147 url = f"{BASE_URL}/api/{VERSION}/sessions/{existing_session_id}/pipe/{pipeline}" 1148 _session = { 1149 "metadata": session_metadata, 1150 "processing_params": params, 1151 } 1152 _session = json.dumps(_session).encode('utf-8') 1153 aes_key_b64 = os.environ.get('AES_KEY') 1154 aes_key_bytes = base64.b64decode(aes_key_b64) 1155 response = requests.post( 1156 url, 1157 data=encrypt_message(_session, aes_key_bytes), 1158 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 1159 ) 1160 if response.status_code == 200: 1161 print("Session successfully created.") 1162 session_id = response.json().get('session_id') 1163 pipeData = response.json().get('pipeData') 1164 # print(session_id) 1165 return session_id, pipeData 1166 else: 1167 print("Session failed:", response.text) 1168 return None 1169 1170 return session_id # processed_data 1171 else: 1172 print("Session failed.") 1173 return False
Process a session pipeline with specified parameters and metadata.
This function processes an existing session by applying a specified pipeline and parameters. It sends a POST request to the API with the session metadata and processing parameters, creating a new session based on the existing one.
Args: pipeline (str): The pipeline to be applied to the session. params (dict): The processing parameters for the pipeline. user_id (str): The user ID (obtained through free registration with HABS). date (str): The date of the session. existing_session_id (str): The ID of the existing session to be processed. session_type (str, optional): The type of the new session. Defaults to an empty string. tags (list, optional): A list of tags associated with the session. Defaults to an empty list.
Returns: tuple: A tuple containing the new session ID and the processed data if the request is successful. None: If the session creation fails. bool: False if the session metadata is invalid.
Example:
new_session_id, processed_data = process_session_pipe("my_pipeline", {"param1": "value1"}, "12345", "2023-07-03", "existing_session_001") print(new_session_id, processed_data)
Notes:
Ensure that the environment variable AES_KEY
is set to the base64 encoded AES key.
Raises: Exception: If there is an error in the request or response.
1179def train(session_id, params, user_id): 1180 """ 1181 Sends a request to the server to train a machine learning algorithm on the data from a specified session. 1182 1183 Args: 1184 **session_id** (*str*): The unique identifier of the session containing the data to be used for training. 1185 **params** (*dict*): The parameters for the training process. 1186 **user_id** (*str*): The user id (obtained through free registration with HABS) 1187 1188 Returns: 1189 *str* or *None*: The task ID if the request is successful, None otherwise. 1190 1191 This function sends the training parameters and session ID to the server, which initiates the training process. 1192 The response includes a task ID that can be used for future interactions related to the training task. 1193 1194 Example: 1195 ``` 1196 train("session_12345", {"param1": "value1", "param2": "value2"}) 1197 ``` 1198 """ 1199 url = f"{BASE_URL}/api/{VERSION}/train/{session_id}" 1200 _params = { 1201 "params": params, 1202 } 1203 _params = json.dumps(_params).encode('utf-8') 1204 aes_key_b64 = os.environ.get('AES_KEY') 1205 aes_key_bytes = base64.b64decode(aes_key_b64) 1206 response = requests.post( 1207 url, 1208 data=encrypt_message(_params, aes_key_bytes), 1209 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 1210 ) 1211 # response = requests.get(url, headers={'X-User-ID':USERID}) # mongo _id for the user document. Communicated at user creation. 1212 1213 if response.status_code == 200: 1214 task_id = response.json().get('task_id') 1215 print("Published. For future interactions, use task_id:",task_id) 1216 return task_id 1217 else: 1218 print("Publish failed:", response.text) 1219 return None
Sends a request to the server to train a machine learning algorithm on the data from a specified session.
Args:
session_id (str): The unique identifier of the session containing the data to be used for training.
params (dict): The parameters for the training process.
user_id (str): The user id (obtained through free registration with HABS)
Returns:
str or None: The task ID if the request is successful, None otherwise.
This function sends the training parameters and session ID to the server, which initiates the training process. The response includes a task ID that can be used for future interactions related to the training task.
Example:
train("session_12345", {"param1": "value1", "param2": "value2"})
1225def infer(data_id, params, user_id): 1226 """ 1227 Sends a request to the server to perform machine learning inference based on a previously trained model, given the data ID. 1228 1229 Args: 1230 **data_id** (*str*): The unique identifier of the data to be used for inference. 1231 **params** (*dict*): The parameters for the inference process. 1232 **user_id** (*str*): The user id (obtained through free registration with HABS) 1233 1234 Returns: 1235 *str* or *None*: The task ID if the request is successful, None otherwise. 1236 1237 This function sends the inference parameters and data ID to the server, which initiates the inference process. 1238 The response includes a task ID that can be used for future interactions related to the inference task. 1239 1240 Example: 1241 ``` 1242 infer("data_12345", {"param1": "value1", "param2": "value2"}) 1243 ``` 1244 """ 1245 url = f"{BASE_URL}/api/{VERSION}/infer/{data_id}" 1246 _params = { 1247 "params": params, 1248 } 1249 _params = json.dumps(_params).encode('utf-8') 1250 # response = requests.post(url, json=_params) 1251 aes_key_b64 = os.environ.get('AES_KEY') 1252 aes_key_bytes = base64.b64decode(aes_key_b64) 1253 response = requests.post( 1254 url, 1255 data=encrypt_message(_params, aes_key_bytes), 1256 headers={'Content-Type': 'application/octet-stream', 'X-User-ID':user_id} 1257 ) 1258 # response = requests.get(url, headers={}) # mongo _id for the user document. Communicated at user creation. 1259 1260 if response.status_code == 200: 1261 task_id = response.json().get('task_id') 1262 print("Published. For future interactions, use task_id:",task_id) 1263 return task_id 1264 else: 1265 print("Publish failed:", response.text) 1266 return None
Sends a request to the server to perform machine learning inference based on a previously trained model, given the data ID.
Args:
data_id (str): The unique identifier of the data to be used for inference.
params (dict): The parameters for the inference process.
user_id (str): The user id (obtained through free registration with HABS)
Returns: str or None: The task ID if the request is successful, None otherwise.
This function sends the inference parameters and data ID to the server, which initiates the inference process. The response includes a task ID that can be used for future interactions related to the inference task.
Example:
infer("data_12345", {"param1": "value1", "param2": "value2"})