.. Autoscoring API Guide master file, created by sphinx-quickstart on Fri Oct 28 18:29:08 2022. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Cerebra - MY Sleep Scoring API Guide ##################################### .. toctree:: :maxdepth: 2 :caption: Table of Contents: index Overview & Quick Start *********************** Cerebra's sleep scoring Application Programming Interface (API) is RESTful to enable programmatic access for scoring sleep studies. The API accepts sleep study data in `European Data Format (EDF) `_ and can process level I, level II, and level III studies. The results returned by the scoring engine, named **MY Sleep Scoring**, depend on the input data provided and can include sleep stages, events (i.e., arousals), and Cerebra's own Odds Ratio Product (ORP). .. note:: Our `Open API specification `_ (PRODUCTION) and `Open API specification `_ (STAGING) describe call specifics. .. warning:: At this time the API has NOT been officially released. Users with access can leverage it to progress their projects from a technical perspective but several processing requirements have not yet been implemented by Cerebra staff so all scoring results should be considered preliminary. .. warning:: MY Sleep Scoring results must be manually reviewed and edited by a RPSGT or physician when used in a clinical setting. MY Sleep Scoring results alone should not be used to provide a clinical diagnosis. Like any Application Programming Interface (API) the MY Sleep Scoring API requires one to possess very rudimentary skills in writing code. It is unnecessary for users to identify as Software Developers, but some experience with basic scripting is required. Although our API has been written in Python, and our example scripts are provided in Python, any programming language can be used that is able to interact with a REST API (i.e., Python, Java, Ruby, C#, Swift, etc). As an API user you will be making GET, POST, and PATCH requests so some knowledge or experience with HTTP protocols and RESTful concepts is required, although these are relatively easy and users can probably glean the basics from our example scripts if you have not done this type of work before. The API is fully documented and comes with OpenAPI docs to help the user see what endpoints exist and exactly how calls to our endpoints are structured. As you will be running code to use the API some form of Integrated Development Environment (IDE) is probably preferred. Some example IDS software include: Visual Studio Code, PyCharm, Jupyter Notebooks, IDLE, and Eclipse (etc.) - all of which are free to download and use. Credentials ============ Users must contact Cerebra development staff to create an account (support@cerebramedical.com), providing their email address. Once the account is created users must visit the `project main page `_ to get their **Refresh token**. Simply click on your profile picture in the upper right hand side of the browser and select **Profile**. Early adopters are encouraged to build their first scripts off the staging environment. Your tokens specific to the staging environment can be found from the `staging project main page `_ . .. image:: _static/images/profile.PNG :width: 275 :alt: Link to tokens via Profile page Refresh tokens are used as part of a two-step process for users to authenticate with the API. Once you have your **Refresh** token users will make a POST call to the API to acquire a **Bearer** token. Bearer tokens also known as **Access** tokens. While **Refresh** tokens never expire, your **Bearer** token will expire every hour (if unused). The bearer token must be included in the header of subsequent requests to the API demonstrated below. Your **Refresh** token should be treated like any other credential. It should be saved securely and only exposed to services that need it. Bearer tokens should be fetched **on demand**. .. code-block:: python headers = {"Authorization": f"Bearer {bearer_token}"} Role-based Access ================== The API incorporates role-based access logic with accounts given one of three types: #. User #. SiteAdmin #. CompanyAdmin The roles provide a means for API-users to share files, channel mappings, and scoring results (all referred to as "objects" here). Companies can have one or more sites, and sites can have one or more users. SiteAdmins can read and write their own objects as well as any objects users from the same site have created. Similarly, CompanyAdmins can read and write their own results as well as any objects API-users with User and SiteAdmin credentials have created. API-users with the "User" role can only read and write their own objects. Companies must be created by Cerebra staff, however, CompanyAdmins can create sites via the POST call to /api/sites. Quick Start ============ We advise all API users to read this documentation from beginning to end. However, for those that just want to jump right in we provide these **Quick Start** instructions. The typical API workflow will proceed as follows: #. POST to receive your Bearer token (/api/token). #. GET a list of files for the current user (/api/file). #. POST to create a study object (/api/study/). #. POST to create a file object and scoringrun associated with the study (/api/file). #. POST a channel mapping dictionary (/api/channelmapping/). #. GET the list of channel mappings to review names or indexes (/api/channelmapping). #. PATCH the channel mapping to the file object created in step 4 (/api/file/{file_id}). #. POST the EDF file to the pre-signed URL provided from step 3 & 7 (pre-signed URL) #. Pour yourself a coffee and wait for the Cerebra infrastructure to score the file (~5 minutes). #. GET a list of files for the study (/api/file). #. GET the sleep scoring event file and report file (/api/file/{file_id}/download). Once the three key API objects are **associated** all API requirments are met and the system will automatically trigger a run of the MY Sleep Scoring engine. .. image:: _static/images/triangle.PNG :width: 500 :alt: Three key API objects include the EDF, a channel mapping, and a scoring run The subsections below provide Python code for the most basic workflow. A short (~10 minute) EDF file is available here for testing (tba). ================================ Part 1 - Study and File Creation ================================ The Python code below runs steps 1 - 4 from the workflow above. .. code-block:: python import requests refresh_token = 'PASTE YOUR REFRESH TOKEN HERE' DOMAIN = "https://mysleepscoring-api-staging.cerebramedical.com" def getAccessToken(): ''' returns the active bearer token for the user ''' data = {"refresh_token" : refresh_token} url = f"{DOMAIN}/api/token" r = requests.post(url=url, json=data) r.raise_for_status() result = r.json()['access_token'] return result def getFiles(bearer_token): ''' gets files for a user ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/file" r = requests.get(url=url, headers=headers) r.raise_for_status() result = r.json() return result def createStudy(data, bearer_token): ''' creates a study object ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/study/" r = requests.post(url=url, headers=headers, json=data) r.raise_for_status() result = r.json() return result def createFile(data, bearer_token): ''' creates a file object type = ['Raw EDF','RPSGT Edits'] ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/file/" r = requests.post(url=url, headers=headers, json=data) r.raise_for_status() result = r.json() return result if __name__ == '__main__': #use refresh token to get access token token = getAccessToken() #get a list of files for the current user all_files = getFiles(token) print(all_files) #create a study object, retain id for future API calls body = { "description": "Jane Doe - Night 1" } study_details = createStudy(body, token) print(study_details) #create a file object to get a pre-signed URL to upload to body = { "name": "Jane Doe EDF", "description": "Level II home PSG", "study_id": study_details['id'], "type": "Raw EDF" } file_details = createFile(body, token) print(file_details) #key values to retain print('\n') print('Study ID = {}'.format(study_details['id'])) print('File ID = {}'.format(file_details['id'])) print('pre-signed URL = {}'.format(file_details['upload_url'])) ================================ Part 2 - Define Channel Mapping ================================ Next, step 5 is run to create a channel mapping which links the signal names (or index positions) in your EDF to Cerebra's expected names. Then that object is checked with a simple GET call. Index positions in the body of the request correspond to the channel ordering within the EDF file (useful when duplicate channel names exist in an EDF). .. code-block:: python import json import requests refresh_token = 'PASTE YOUR REFRESH TOKEN HERE' DOMAIN = "https://mysleepscoring-api-staging.cerebramedical.com" def getAccessToken(): ''' returns the active bearer token for the user ''' data = {"refresh_token" : refresh_token} url = f"{DOMAIN}/api/token" r = requests.post(url=url, json=data) r.raise_for_status() result = r.json()['access_token'] return result def createChannelMapping(data, bearer_token): ''' creates a channel mapping object ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/channelmapping/" r = requests.post(url=url, headers=headers, json=data) r.raise_for_status() result = r.json() return result def getChannelMappings(bearer_token): ''' creates a channel mapping object ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/channelmapping" r = requests.get(url=url, headers=headers) r.raise_for_status() result = r.json() return result if __name__ == '__main__': #use refresh token to get access token token = getAccessToken() #define channel mappings for your EDF configuration and scoring settings body = { "name": "Sample PSG Channels", "eeg_left": {"channel_name": "C3-A2","channel_index": 0}, "eeg_right": {"channel_name": "C4-A1","channel_index": 1}, "eye_left": {"channel_name": "LOC-A2","channel_index": 4}, "eye_right": {"channel_name": "ROC-A1","channel_index": 5}, "chin": {"channel_name": "EMG1-EMG2","channel_index": 6}, "ekg": {"channel_name": "ECG1-ECG2","channel_index": 7}, "resp_cannula": {"channel_name": "nFlow","channel_index": 8}, "resp_chest": {"channel_name": "Thoracic","channel_index": 10}, "resp_abdomen": {"channel_name": "Abdominal","channel_index": 11}, "oxygen_sat": {"channel_name": "SAO2","channel_index": 13}, "resp_thermistor": {"channel_name": "therm","channel_index": 9} } #create object url_val = createChannelMapping(body, token) #get all channel mappings available to user all_cms = getChannelMappings(token) #retain the channel mapping ID for subsequent calls print('\n') print('channel_mapping_id = {}'.format(url_val['id'])) =================================== Part 3 - Associate Channel Mapping =================================== The code below runs step 7 from the simplified workflow to link the file object to the proper channel mapping. The channel mapping ID (70) and file ID (261) are from previous steps (see Part 2 & 3), your ID values will differ. .. code-block:: python import requests refresh_token = 'PASTE YOUR REFRESH TOKEN HERE' DOMAIN = "https://mysleepscoring-api-staging.cerebramedical.com" def getAccessToken(): ''' returns the active bearer token for the user ''' data = {"refresh_token" : refresh_token} url = f"{DOMAIN}/api/token" r = requests.post(url=url, json=data) r.raise_for_status() result = r.json()['access_token'] return result def patchFileAndChannelMapping(data,file_id, bearer_token): ''' creates the association between a file and a channel mapping object ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/file/{file_id}" r = requests.patch(url=url, headers=headers, json=data) r.raise_for_status() result = r.json() return result if __name__ == '__main__': #use refresh token to get access token token = getAccessToken() body = {"channelmapping_id": 70} file_id = 261 url_val = patchFileAndChannelMapping(body, file_id, token) print(url_val) #retain the pre-signed URL print('\n') print('upload_url = {}'.format(url_val['upload_url'])) ========================= Part 4 - Patch Scoringrun ========================= Once the EDF file is uploaded all three primary objects are in place to start scoring. Therefore, before your EDF is uploaded we recommend using a PATCH call to the scoring run object to set various run settings. The two most important settings here are the **collection_system** and the **prefilter** flags. .. code-block:: python import json import requests refresh_token = 'PASTE YOUR REFRESH TOKEN HERE' DOMAIN = "https://mysleepscoring-api-staging.cerebramedical.com" def getAccessToken(): ''' returns the active bearer token for the user ''' data = {"refresh_token" : refresh_token} url = f"{DOMAIN}/api/token" r = requests.post(url=url, json=data) r.raise_for_status() result = r.json()['access_token'] return result def getFile(file_id, bearer_token): ''' get a file object, used to get the scoring run id ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/file/{file_id}" r = requests.get(url=url, headers=headers) r.raise_for_status() result = r.json() return result def patchScoringRun(data,scoringrun_id, bearer_token): ''' creates the association between a file and a channel mapping object ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/scoringrun/{scoringrun_id}" r = requests.patch(url=url, headers=headers, json=data) r.raise_for_status() result = r.json() return result if __name__ == '__main__': #use refresh token to get access token token = getAccessToken() #from Part 1 file_id_val = 599 #get file file_info = getFile(file_id_val, token) #first item in the list is the default scoring run default_scoring_run_id = file_info['scoringruns'][0]['id'] #patch scoring run body = { "lights_off_epoch" : 7, "lights_on_epoch" : 97, "co2units": 'mmHg', "prefilter": 1, "collection_system": "Alice G3" } url_val = patchScoringRun(body, default_scoring_run_id, token) print('\n') print('scoringrun id = {}'.format(default_scoring_run_id)) =================== Part 5 - Upload EDF =================== The script below uploads the EDF file from your local disk to Cerebra's cloud using a pre-signed URL. In this example we have truncated the pre-signed URL (the actual URL will be very long). Ensure you use the full URL string from step 3 when running your code. .. code-block:: python import json import requests refresh_token = 'PASTE YOUR REFRESH TOKEN HERE' DOMAIN = "https://mysleepscoring-api-staging.cerebramedical.com" def getAccessToken(): ''' returns the active bearer token for the user ''' data = {"refresh_token" : refresh_token} url = f"{DOMAIN}/api/token" r = requests.post(url=url, json=data) r.raise_for_status() result = r.json()['access_token'] return result def uploadEDF(edf_file, presigned_url, bearer_token): ''' upload an edf file to the URL ''' headers = {"Authorization": f"Bearer {bearer_token}"} files = {'file': open(edf_file, 'rb')} r = requests.post(url=presigned_url, files=files) print(f"upload file to gcs status_code:{r.status_code}") return r.status_code if __name__ == '__main__': #use refresh token to get access token token = getAccessToken() file_to_upload = r'C:\data\Jane Doe\12345\PSG.edf' upload_url = 'https://storage.googleapis.com/scalingorp-fileuploads-cerebra-staging-ca/edf_file_upload/XX/files/XXX/XXXXX' up_val = uploadEDF(file_to_upload, upload_url, token) #successful uploads should result in a 204 print(up_val) ========================= Part 6 - Download Results ========================= Here the event file and reporting results for a particular study are downloaded, in this case the files for study ID = 72. .. code-block:: python import json import os import requests refresh_token = 'PASTE YOUR REFRESH TOKEN HERE' DOMAIN = "https://mysleepscoring-api-staging.cerebramedical.com" def getAccessToken(): ''' gets an access token ''' data = {"refresh_token" : refresh_token} url = f"{DOMAIN}/api/token" r = requests.post(url=url, json=data) r.raise_for_status() result = r.json()['access_token'] return result def downloadAutoScoringJson(file_id, bearer_token): ''' get the json results for MY Sleep Scoring from Cerebra ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/file/{file_id}/download" r = requests.get(url=url, headers=headers) r.raise_for_status() data = r.json() return data def getScoringRun(scoringrun_id, bearer_token): ''' get a file object, used to get the scoring run id ''' headers = {"Authorization": f"Bearer {bearer_token}"} url = f"{DOMAIN}/api/scoringrun/{scoringrun_id}" r = requests.get(url=url, headers=headers) r.raise_for_status() result = r.json() return result if __name__ == '__main__': #use refresh token to get access token token = getAccessToken() scoringrun_id_val = 164 out_dir = r'C:\data\Jane Doe\12345' #what file types you want to download download_types = ['Autoscoring Events','Report Data'] scoring_run_info = getScoringRun(scoringrun_id_val, token) if scoring_run_info['status'] == 'Reporting Complete': for file in scoring_run_info['files']: file_id = file['id'] f_type = file['type'] file_name = file['study']['description'] #this should be your folder name out_file_folder = os.path.join(out_dir, file_name) if not os.path.exists(out_file_folder): os.mkdir(out_file_folder) if file['type'] in ['Autoscoring Events', 'Report Data'] and file['type'] in download_types: print(f'\tfrom {file_name} downloading {f_type}') out_f = os.path.join(out_file_folder, f'{file_name}_{f_type}.json') if not os.path.exists(out_f): my_json = downloadAutoScoringJson(file_id, token) with open(out_f, 'w') as out_file: json.dump(my_json, out_file) else: print('\t{} {}'.format(scoring_run_info['id'], scoring_run_info['status'])) print('\t\t>> re-run this script in 5 min') print('\t\t\t{}'.format(datetime.datetime.now())) ============================= Part 7 - Access and Graph ORP ============================= Lastly, we will extract the ORP values and create two plots, one showing a simple ORP time series and the second an ORP architecture graph. .. code-block:: python import json import matplotlib.pyplot as plt def graph_architecture(stats, png=None): ''' creates a simple bar plot showing ORP architecture ''' y_vals = [] x_order = ['000_025','025_050','050_075','075_100','100_125','125_150','150_175','175_200','200_225','225_250'] for x_val in x_order: if x_val in stats["orp"]["summary"]["total_study_percent"].keys(): y_vals.append(stats["orp"]["summary"]["total_study_percent"][x_val]) else: y_vals.append(0) bad_data = stats["orp"]["summary"]["total_study_percent"]["bad"] plt.bar(x=range(len(x_order)), height=y_vals, label='ORP', color='#3AA0F5') plt.bar(x=10, height=bad_data, label='bad data', color='#D3D3D3') plt.ylabel('% of sleep study') plt.title('ORP Architecture') plt.tick_params(axis='x', which='both', bottom=True, top=False, labelbottom=True) plt.xticks(ticks=[0,1,2,3,4,5,6,7,8,9,10], labels=['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'n/a']) plt.legend() plt.tight_layout() if png: plt.savefig(png) plt.clf() else: plt.show() def graph_orp(orp1, orp2, png=None): ''' creates a simple graph showing an ORP time series ''' plt.rcParams["figure.figsize"] = (8,3) plt.plot(orp1, label='EEG1') plt.plot(orp2, label='EEG2') plt.xlabel('Epoch') plt.ylabel('ORP') plt.title('30-second ORP values') plt.ylim([0,2.5]) plt.legend() plt.tight_layout() if png: plt.savefig(png) plt.clf() else: plt.show() def compute_orp_30s(x): ''' computes 30-second average ORP values, skipping invalid epochs labelled as -1 ''' set_lengh = 10 averages = [] size_x = len(x) for i in range(0, len(x), set_lengh): subset = x[i:min(i+set_lengh, size_x)] all_valid = [value for value in subset if value != -1] if all_valid: average = sum(all_valid) / len(all_valid) else: average = None averages.append(average) return averages def process_orp(events, report): ''' read and graph 30-second ORP and ORP architecture ''' event_info = json.load(open(events)) report_info = json.load(open(report)) eeg1_orp_3sec = event_info['EEG1ORPs'] eeg2_orp_3sec = event_info['EEG2ORPs'] eeg1_orp = compute_orp_30s(eeg1_orp_3sec) eeg2_orp = compute_orp_30s(eeg2_orp_3sec) graph_orp(eeg1_orp, eeg2_orp) graph_architecture(report_info) if __name__ == '__main__': #point these to the two JSON files you downloaded in step 6 in_json_events = r'C:\data\Jane Doe\12345\12345_Autoscoring Events.json' in_json_report = r'C:\data\Jane Doe\12345\12345_Report Data.json' process_orp(in_json_events, in_json_report) print('done') API Objects ************ Study Objects ============== The POST call to /api/study/ creates an object representing a sleep study. The request body includes a single item API users can leverage to tag a study. This description is free-form text that can be used for subsequent study lookup. Sleep studies can hold one or more EDF files, although maintaining a 1:1 ratio of study to EDF is recommended when learning the API. #. description .. code-block:: python body = {'description': 'Jane Doe - Night 1'} EDF Channels ============= To score a study and return sleep stages (i.e., Wake, N1, REM, etc.) or other metrics (i.e., ORP NREM) the EDF file must contain at least one EEG channel. The results of Cerebra's MY Sleep Scoring will differ depending on what channels are in the EDF file (and mapped). MY Sleep Scoring can utilize up to two EEG channels during a single scoring run, along with a multitude of additional channel types. The unit and minimum sampling rate (frequency) requirements for all channels types are listed below (Table 1). It is important to note that the API will resample channels to the required sampling rates, the values in Table 1 are **mimimum** values. .. warning:: Two **Central** EEG channels should be mapped for best results. Frontal EEG can also be used but will provide less accurate results. Occipital EEG's should not be mapped. .. warning:: At this time, the API requires signals to have already undergone re-referencing. Although there are different types of rereferencing an example is simple subtraction (rereferencing) of Central EEG channels to a Mastoid EEG (i.e., C3-A2). .. list-table:: Table 1. Channel Requirements by Type for EDF Files :widths: 25 20 20 :header-rows: 1 * - Channel Type - Units - Minimum Frequency [Hertz] * - Audio^ - undefined - 120 (50) * - Cannula - undefined - 20 * - EEG - uV, mV, or V - 120 * - EMG (Chin) - uV, mV, or V - 120 * - EMG (Legs) - uV, mV, or V - 120 * - EMG (Intercostal) - uV, mV, or V - 120 * - EKG - uV, mV, or V - 120 * - EOG - uV, mV, or V - 120 * - Effort Belts (Chest, Abdomen) - uV - 20 * - Oxygen Saturation (SPO2) - % - 1 * - Pulse - beats per minute - 1 * - Body Position - undefined - 1 * - Thermistor - undefined - 20 * - CPAP Flow^^ - undefined - 20 * - EPAP (Expiratory pressure)^^^ - cmH2O - 20 * - IPAP (Inspiratory pressure)^^^ - cmH2O - 20 * - End Tidal CO2 - mmHg or % - 20 * - Transcutaneous CO2 - mmHg or % - 20 ^ Audio input should be no lower than 500 Hz, however, Cerebra's Prodigy 2 files have undergone propriatery processing and are saved at 50 Hz. ^^ The CPAP Flow signal data is analyzed using relative signal characteristics so the units are undefined. ^^^ Users can provide a scaling factor if the airway pressure channels (both inspiratory and expiratory) were not recorded in cm of water (see Table 3). Channel Mapping ================ Individual channels in an EDF can be named anything using 128 characters or less. Each individual name must, however, be linked to a set of key names established by Cerebra using a **Channel Mapping** dictionary. The key names in the channel mapping and their association back to channel types is presented in Table 2 below. Channel mappings can exist on their own, but eventually must be associated to a **file** object. To view all of the channel mappings your user is currently authorized to see users can make a GET request to /api/channelmapping. .. list-table:: Table 2. Channel Mapping Key Names and Type :widths: 20 25 :header-rows: 1 * - Key Name - Channel Type * - audio - Audio * - chin - EMG (chin) * - eeg_left - EEG * - eye_left - EOG * - eeg_right - EEG * - eye_right - EOG * - ekg - EKG * - leg_left - EMG (legs) * - leg_right - EMG (legs) * - mastoid - EEG * - oxygen_sat - Oxygen Saturation (SPO2) * - pulse - Pulse * - position - Body Position * - position_left_value - *integer* * - position_prone_value - *integer* * - position_right_value - *integer* * - position_supine_value - *integer* * - position_uknown_value - *integer* * - position_upright_value - *integer* * - resp_abdomen - Belt * - resp_cannula - Cannula * - resp_chest - Belt * - resp_thermistor - Thermistor * - cpapflow - CPAP Flow * - epap - EPAP (Expiratory pressure) * - ipap - IPAP (Inspiratory pressure) * - etc02 - End Tidal CO2 * - transc02 - Transcutaneous CO2 An example channel mapping is shown below. Notice how in this particular example not all of the channel types are included. In fact, this mapping only contains 7 channels in total. If specified, the channel_index integer for each channel must match the order the channels are saved in the EDF itself. The API will read the EDF channels following the index, which has been built into the API because EDF's can often have **duplicate** channel names. .. code-block:: python data = { "name": "a string for user convenience" "eeg_left": {"channel_name" : "EEG O1-A2", "channel_index": 0}, "eye_left": {"channel_name" : "EOG ROC-A2", "channel_index": 1}, "resp_cannula": {"channel_name" : "Airflow", "channel_index": 9}, "resp_chest": {"channel_name" : "Effort-Chest", "channel_index": 10}, "oxygen_sat": {"channel_name" : "SpO2", "channel_index": 4}, "pulse": {"channel_name" : "Heartbeat", "channel_index": 5}, "audio": {"channel_name" : "Snore", "channel_index": 6}, } .. warning:: The Channel Mapping object uses 0-based indexing. So the first channel in the EDF must be set to channel_index = 0. The second channel in the EDF will be set to channel_index 1 (and so on). To check the channel ordering in your EDF we recommend the Python library `pyedflib `_. With just a few lines of Python code you can easily confirm the channel names and ordering. Of course, EDF headers are written in plain text so you can also examine the channel details using any text editor such as Notepad or Notepad++. With some files users will need to consult additional signal header information to ensure mapping with the proper channels (i.e., transducer or dimension). .. code-block:: python import pyedflib in_file = r'C:\data\Jane Doe\12345\PSG.edf' header_dict = pyedflib.highlevel.read_edf_header(in_file) for idx, sig_header in enumerate(header_dict['SignalHeaders']): print(idx, sig_header['label']) #print('{},{},{},{}'.format(idx, sig_header['label'], sig_header['dimension'], sig_header['transducer'])) To assign a channel mapping to a file object you will utilize a PATCH call to /api/file/{file_id}. The path parameter for this call indicates to which file object the channel mapping will be associated. The channel mapping ID is included in the requst body. .. code-block:: python body = {'channelmapping_id': 70} The **position** keys in the channel mapping object provides a means to acquire aggregate reporting statistics. For example, AHI values when in supine (laying on your back) vs. prone (laying on your stomach) sleep positions. No assumptions are made by the API for body position encoding, including for Prodigy 2 files. Cerebra's default body position mapping is as follows and should be used when processing Prodigy 2 files. .. code-block:: python { 'channel_name': "Position", 'channel_index': "will vary depending on the configuration", 'supine_value': 1, 'prone_value': 2, 'left_value': 3, 'right_value': 4, 'upright_value': 7, 'unknown_value': 0 } Scoring Run Objects ==================== The POST call to /api/file will create a default **scoringrun**. The scoring run provides users the ability to affect the MY Sleep Scoring results by changing various settings. For example, it is via the scoring run that a user can tell the system whether or not the EEG signals have already undergone standard `AASM `_ pre-filtering. Thirteen scoringrun settings are provided with each described in Table 3. .. warning:: Specifying the **collection_system** is critical when processing Prodigy2 files. This string can effect signal processing and scoring logic for file types that have undergone joint collaboration with Cerebra's Development team. .. list-table:: Table 3. Run settings on Scoringrun Objects :widths: 20 10 10 30 :header-rows: 1 :class: tight-table * - Key Name - Type - Default Value - Description * - lights_off_epoch - integer - 1 - 1-based integer used to specify the starting point for analysis. Epochs here are 30-seconds in duration. If only date-time (yyyy-mm-ddTHH:MM:SS) values are known see epochlookup utility function on /file/{file_id}/epochlookup. * - lights_on_epoch - integer - -1 - 1-based integer used to specify the end point for analysis. If you want to analyze the entire file simply set "lights_off_epoch" to 1 and "lights_on_epoch" to -1. If only date-time (yyyy-mm-ddTHH:MM:SS) values are known see epochlookup utility function on /file/{file_id}/epochlookup. * - **collection_system** - string - "" - The name of the physical device used to collect the biosignals (i.e., Alice, Sandman, Embla, prodigy2, IDUN Guardian). See **Warning** above. * - hypopnea_criteria - string - "B" - Can also be set to "A". A = Acceptable, B = Recommended. See AASM scoring manual for definitions. * - sa02lookback - integer - 90 - Amount of time in seconds to seek in a backward direction from the trough of the SaO2 to look for a corresponding peak, to determine if there is a desaturation; supported values are 60 or 90. * - sa02lookforward - integer - 0 - Amount of time in seconds to seek in a forward direction from the trough of the SaO2 to look for a corresponding peak, to determine if there is a desaturation; supported values are 0 or 30. * - respitracetype - integer - 1 - Used to toggle between inductance and non-inductance respiratory effort belts. If inductance (i.e. RIP belts) set to 1, if non-inductance (e.g. piezoresistive) set to 0. Supported values 1 or 0. * - nasalprocessor - integer - 0 - Used to toggle between processed and unprocessed nasal cannula data. If the nasal cannula channel requires processing by the square root function set this to 1. Setting to 1 is required when the nasal cannula is strictly measuring nasal pressure without any signal processing applied (as the nasal pressure measured is proportional to the square of nasal flow). In most cases nasal root is not used and the setting should be 0. Stated another way, if the Cannula is in units of pressure send in 1. If the Cannula is in units of Flow send in 0. * - primaryrespiratorysignal - string - "F" - Used to toggle between flow and airway type channels as the main signal type. The PrimaryRespiratorySignal is a string that can be set to one of "F" or "A". The default should be “F”. No values other than “F” (Flow) and “A” (Airway pressure) are accepted. * - co2units - string - % - If the EDF file contains a signal representing the end tidal CO2 concentration of exhaled air, users can toggle between two CO2 units, including % and mmHg. The default value should %. "mmHg" is also accepted. * - cpapscaler - integer - 1 - The Airway Pressure Scaling Factor (cpapscaler) allows modification of the Airway Pressure channels (both Inspiratory and Expiratory) to allow for alterations in the gain of these channels. This feature was enabled in case files that are exported from data acquisition systems that measure pressure in units other than cm of water. Where the conversion factor is known, this feature can implement the required adjustment. Values must be greater than zero. NOTE: see **cpapscaltertype** * - cpapscalertype - string - ** - Used to toggle between multiplication ("*") or division ("/") for the mathematical operator for cpapscaler. * - **prefilter** - integer - 1 - A boolean (1 or 0) used to indicate whether the channels in the EDF have already been subjected to AASM pre-filtering (i.e., high-pass filters, low-pass filters, etc.). 1 means the signals have been filtered according to AASM specifications. .. note:: Users are referred to the `American Academy of Sleep Medicine `_ for pre-filtering specifics. .. note:: At this time only one set of lights tags is supported per run. As a result, split night studies must be processed as two seperate runs (or studies). The dictionary below provides an example of the scoringrun content. .. code-block:: python body = { 'lights_off_epoch' : 24, 'lights_on_epoch' : -1, "collection_system": "prodigy2", "hypopnea_criteria": "B", "sa02lookback": 90, "sa02lookforward": 0, "respitracetype": 1, "nasalprocessor": 0, "primaryrespiratorysignal": "F", "co2units": "%", "cpapscaler": 1, "cpapscalertype": "*", "prefilter": 1 } File Objects & Pre-Signed URLs =============================== The File object is the center piece of the MY Sleep Scoring API endpoints. The return from /api/file/ or /api/file/{file_id} includes file, study, and **scoringrun** information. At the end of the processing chain a Study can have several file types associated with it, including: #. **Raw EDF** #. Resampled EDF #. Autoscoring Events #. Reporting Data #. **RPSGT Edits** API users can only create file objects of type **Raw EDF** and **RPSGT Edits**. All other file types are generated by the system. The POST call to /api/file/ will return a dictionary with a key called "upload_url" that contains the web address for you to upload files (Raw EDF or RPSGT Edits). Details for that process are below in the File Uploads section of this document. When making the POST call to retreive a pre-signed URL the request body can include several optional strings, including: #. name #. description #. study_id #. type The name and description items are for user-convenience only. These can be used to tag files with names or other relevant information for the user. The **study_id** is used to associate a File and Study objects. Type is a string that must be set to **Raw EDF** or **RPSGT Edits**. .. code-block:: python body = {'name': 'Janes third night', 'description': 'Home PSG', 'study_id' : 71, 'type' : 'Raw EDF'} File Uploads ============= The Python code below provides a snippet for a simple EDF file upload. Please note, the actual pre-signed URL has been truncated for this documentation. .. code-block:: python edf_file = 'insert a path to a local EDF file here' presigned_url = 'https://storage.googleapis.com/scalingorp-fileuploads-cerebra-staging-ca/edf_file_upload/XX/files/XXX/XXXXX' files = {'file': open(edf_file, 'rb')} r = requests.post(url=presigned_url, files=files) print(f"upload file to gcs status_code:{r.status_code}") .. warning:: Presigned URLs will expire in 7 days if not utilized. Converting Datetime Values to Integer Epochs ============================================= The MY Sleep Scoring API provides a utility function for users to convert datetime values (i.e., 2008-04-06T21:41:56) into an integer epoch value for specific files. This is very helpful for users to patch scoringrun properties (lights_off_epoch & lights_on_epoch). The utility function is available from the file object at /api/file/{fileid}/epochlookup?lights_off=yyyy-mm-ddTHH:MM:SS&lights_on=yyyy-mm-ddTHH:MM:SS. Run Status & Results ********************* Scoring Run Status ============ Depending on which API calls have been made Scoring Run objects can be at any number of conditions. The table below provides each staus and a short defintion. .. list-table:: Table 4. File Status Values :widths: 25 50 :header-rows: 1 :class: tight-table * - Status Message - Definition * - Created - A file object exists, this is the first step of the process which returns a pre-signed URL. * - Upload Complete - An EDF file has been uploaded to Cerebra's cloud storage at the location of the pre-signed URL. * - Channel Mapping Attached - A file upload object has been created and the names of the channels in the EDF are linked back to the required key names (Table 2). * - Validation Complete - The EDF file uploaded and the system was able to read the EDF including the file duration from the header and the various channel names. The next step is to assign a channel mapping with a Patch call. * - Validation Error - The EDF file was unable to be resampled for scoring, either due to an error in the EDF file or the file did not meet the minimum requirements (Table 1). * - Ready for Scoring - All conditions have been met. At this point the system will automatically start a MY Sleep Scoring run. The analysis typically takes between 3 and 8 minutes. * - Scoring in Progress - The scoring process has begun. The system will automatically update the file status to **Scoring Complete** when finished. * - Scoring Complete - My Sleep Scoring has completed its analysis and the results, referred to as **Autoscoring Events** can be downloaded. * - Scoring Error - There was an issue running the file. * - Reporting Complete - The reporting engine, which computes statistics like AHI, Sleep Latency, etc. has completed and the results can be downloaded. **This status marks the end of all processing and signified a completed run**. * - Reporting Error - There was an issue taking the results from the MY Sleep Scoring run and computing the statistics. This could be due to server error or rare cases where the EDF file has unexpected channels. MY Sleep Scoring Results ========================= The results from Cerebra's MY Sleep Scoring are accessed via the associated file ID. Making a GET request to **/api/file/{file_id}/download** will return either a JSON object or a binary stream. As described in the section **File Objects & Pre-Signed URLs** the two main result types are **Autoscoring Events** and **Reporting Data**. ======= Events ======= Files of type **Autoscoring Events** are large JSON objects with the **raw** MY Sleep Scoring features. Utilizing Autoscoring Event files will require you take some time to familiarize yourself with the JSON structure presented below. Care should be taken to examine the JSON file as the contents are dependant on which channels were present in the EDF and mapped with the API. Detailed ORP values at 3-second and 30-second time intervals are included here. ORP summary values are provided in the report output (next section). The top-level keys of this events file are presented below. .. code-block:: python { "UseBadRespiratoryFromEvents": True, "Events": [], "ReadOnly": True, "LightsOffset": 0.0, "LightsOn": 28170.0, "Version": "1.02.020", "ReleaseDate": "15-Jan-22", "HypopneaType": "B", "HeartRateData": [], "DeltaPowers": [], "AverageORPs": [], "FullORPs": [], "EEG1ORPs": [], "EEG2ORPs": [], "Breaths": [], "Desaturations": [], "DeltaDurations": [], "Spindles": [], "REMTimes": [], "BadChannels": [], "InvalidEvents": [], "Versions": {}, "ReleaseDates": {} } Table 5 provides a brief definition for each of the high-level components of the Autoscoring results JSON file. .. list-table:: Table 5. Autoscoring Results Dictionary Key :widths: 25 50 :header-rows: 1 :class: tight-table * - Key Name - Definition * - UseBadRespiratoryFromEvents - A boolean flag used by Cerebra's Viewer application. If set to 1, then Viewer uses BadRespiratory events from the list of events, otherwise it calculates its own BadRespiratory events. * - Events - A list of all event types including arousals, hypopneas, snoring, desaturations, sleep stages, etc. * - ReadOnly - A deprecated setting internal to a Cerebra application. * - LightsOffset - The number of seconds from the start of the EDF file to where epoch 1 starts (units = seconds). * - LightsOn - The number of seconds from LightsOffset to the end of the scoring. Note, this may not be the end of the file because it was altered by an technician upon human review. * - Version - The major.minor.patch release version of MY Sleep Scoring used to process the EDF. * - HypopneaType - See hypopnea_criteria in Table 3. * - HeartRateData - A list providing the average computed heart rate for each 30-second epoch. * - DeltaPowers - A dictionary for each EEG channel proving a list of power values (custom scaled by Cerebra) for each 30-second epoch. * - AverageORPs - Currently this is always an empty list. The intention is this will hold the 30-second ORP values. * - FullORPs - A list of dictionaries with the 3-second epoch ORP values for each 30-second epoch. Each 30-second epoch is processed in 3 second chunks by our ORP algorithm. * - EEG1ORPs - A list of 3-second ORP values from the "eeg_left" channel. 3-second epochs classified as "BAD" (due to poor waveform characteristics) have been encoded as -1.0. * - EEG2ORPs - A list of 3-second ORP values from the "eeg_right" channel. 3-second epochs classified as "BAD" (due to poor waveform characteristics) have been encoded as -1.0. * - Breaths - A list of breathing events. * - Desaturations - A list of times and durations when the blood oxygen decreased by certain levels. Values are reported at integer values > 1% (i.e., 1%, 2%, 3%, etc). * - DeltaDurations - A of list of Delta waves in each 30 second epoch that were identified in each EEG channel ("eeg_left" and "eeg_right"). * - Spindles - A of list of waveforms characterized as spindles in each EEG channel ("eeg_left" and "eeg_right"). * - REMTimes - A of list of times (seconds) when signal conditions were such tha tthe algorithm classified REM. * - BadChannels - A of list EDF channel names that were deemed unacceptable acorss a range of conditions. * - InvalidEvents - If a particular channel is flagged as "BAD" (i.e., oxygen_sat) then all desaturation events are decared as invalid. * - Versions - The version of My Sleep Scoring used for the scoring result. If the file was edited, by a RPSGT or Physician using Viewer, it will include a "ViewerLite" key. * - ReleaseDates - Redundant with the "ReleaseDate" key. ============ Report Data ============ Downloaded files of type **Reporting Data** provide summary statistics from the MY Sleep Scoring results. The JSON structure includes nine categories of data. The contents of each dictionary is defined in the subsequent tables. .. code-block:: python { "sleep_variables": {}, "stage_time": {}, "arousals": {}, "legs": {}, "orp": {}, "heart_rate": {}, "respiratory": {}, "oxygen": {}, "snoring": {} } .. warning:: This section of the reporting documentation is a work-in-progress. Your feedback would be most welcome. .. list-table:: Table 6. Autoscoring Report Data - Sleep Variables :widths: 20 20 10 50 :header-rows: 1 :class: tight-table * - Parent Key Name - Child Key - Unit - Description * - sleep_variables - start_time - datetime - Either the startdate from the EDF header or the user-specified time in lights_off_epoch * - sleep_variables - end_time - datetime - Time defined by lights_on_epoch, or the time inherent to the EDF end of file, if lights_on_epoch default (-1) was used * - sleep_variables - tst - seconds - Total Sleep Time (tst) is the sum of all sleep event durations during the study. Typically follow epoch boundaries, but the start and end may be clipped by lights off and lights on. * - sleep_variables - efficiency_percent - % - Sleep Efficiency is Total Sleep Time divided by the sum of all epoch event durations (excluding BadEEG) between lights on and lights off. If there are no valid epochs (e.g. denominator is zero), reports N/A. * - sleep_variables - waso - seconds - Wakefulness After Sleep Onset (WASO) is the The amount of time spent in wake following sleep onset. Computed as total recording time minus sleep latency minus total sleep time. Note that TRT in this particular calculation must exclude Bad EEG epochs following sleep onset, to avoid overreporting wake. * - sleep_variables - rem_latency - seconds - Rem Latency is the amount of time between the onset of sleep and the first REM stage. N/A if no REM was recorded. * - sleep_variables - sleep_latency - seconds - Sleep Latency is the time between lights_off_epoch and the first stage of sleep. N/A if no sleep was recorded. * - sleep_variables - rem_latency_minus_wake - seconds - Same as REM Latency, but with any wake durations during that time span subtracted away. N/A if no REM was recorded. * - sleep_variables - stage_shifts - number of epochs - Counts transitions between distinct stages from lights out to lights on. Note that Bad EEG events are ignored (in the sense that they don't count as a transition). * - sleep_variables - awakenings - count - Count the number of awakening events. These are computed as transitions from a non-wake state to a wake state. Note that BadEEG counts as a non-wake stage. .. list-table:: Table 7. Autoscoring Report Data - Stage Time :widths: 20 20 50 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - Time (min) - double - Sum of all durations of sleep epochs in stage listed in row header, existing between lights off and lights on. * - TST (%) - double - Time (above) divided by total sleep time, reported as a percentage. See Sleep Variables table for total sleep time calculation. Not calculated for stage awake. * - Latency (min) - double - Difference between study start (lights out) and first epoch matching stage listed in row header. Not calculated for stage awake. N/A if there are no epochs corresponding to the stage in the row header. NOTE: REM Latency is equal to REM Latency computed in Sleep Variables (computed from Sleep Latency), NREM Latency is equal to Sleep Latency computed in Sleep Variables. .. list-table:: Table 8. Autoscoring Report Data - Arousals :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - Respiratory - Total/REM/NREM - Number - int - The total number of arousals occuring during sleep only, and which are associated with a respiratory event. Respiratory arousals include awakenings. There must be at least one good respiratory channel in the study, otherwise, these arousals are ignored. Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). * - Respiratory - Total/REM/NREM - Index - double - The total number of respiratory arousals per hour, computed as the value above divided by total time (hr). Total time for Total = (N1, N2, N3, REM), for REM = (REM), for NREM = (N1, N2, N3). Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). * - PLM - Total/REM/NREM- Number - int - The total number of arousals occuring during sleep only, and which are associated with a PLM event. PLM arousals include awakenings. There must be at least one good leg channel in the study, otherwise, these arousals are ignored. Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). * - PLM - Total/REM/NREM- Index - double - The total number of PLM arousals per hour, computed as the value above divided by total time (hr). Total time for Total = (N1, N2, N3, REM), for REM = (REM), for NREM = (N1, N2, N3). Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). * - Spontaneous - Total/REM/NREM - Number - int - The total number of arousals occuring during sleep only, and which are NOT associated with a PLM event or respiratory event. Spontaneous arousals include awakenings. NOTE: spontaneous arousals never include respiratory (or PLM) associated arousals, even if the corresponding respiratory and PLM channels are all marked bad. In that case, the associated arousals are simply ignored. Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). * - Spontaneous - Total/REM/NREM - Index - double - The total number of spontaneous arousals per hour, computed as the value above divided by total time (hr). Total time for Total = (N1, N2, N3, REM), for REM = (REM), for NREM = (N1, N2, N3). Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). * - Total - Total/REM/NREM - Number - int - The sum of respiratory, PLM, and sponatenous arousals, as computed above. Awakenings that are not associated with respiratory or PLM events are not included. Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). * - Total - Total/REM/NREM - Index - double - The total number of arousals per hour, computed as the value above divided by total time (hr). Total time for Total = (N1, N2, N3, REM), for REM = (REM), for NREM = (N1, N2, N3). Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM). .. list-table:: Table 9. Autoscoring Report Data - Legs :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - PLMs - Total/REM/NREM - Number - int - The total number of PLM events occuring during sleep only, regardless of arousal association. This includes "chain"-computed PLM events that are computed over wake stages and bad eeg events. Note that this should be reported as N/A if there are no good leg channels. Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM), or if both leg channels are bad or missing. * - PLMs- Total/REM/NREM - Index - double - The total number of PLM events per hour, computed as the value above divided by total time (hr). Total time for Total = (N1, N2, N3, REM), for REM = (REM), for NREM = (N1, N2, N3). Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM), or if both leg channels are bad or missing. * - With Arousals - Total/REM/NREM - Number - int - The total number of PLM events occuring during sleep only, and that are associated with an arousal. This includes "chain"-computed PLM events that are computed over wake stages and bad eeg events. Arousals in this case include awakenings. Note that this should be reported as N/A if there are no good leg channels. Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM), or if both leg channels are bad or missing. * - With Arousals - Total/REM/NREM - Index - double - The total number of arousal-associated PLM events per hour, computed as the value above divided by total time (hr). Total time for Total = (N1, N2, N3, REM), for REM = (REM), for NREM = (N1, N2, N3). Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM), or if both leg channels are bad or missing. * - Without Arousals - Total/REM/NREM - Number - int - The total number of PLM events occuring during sleep only, and that are not associated with an arousal. This includes "chain"-computed PLM events that are computed over wake stages and bad eeg events. Arousals in this case include awakenings. Note that this should be reported as N/A if there are no good leg channels. Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM), or if both leg channels are bad or missing. * - Without Arousals - Total/REM/NREM - Index - double - The total number of non-arousal-associated PLM events per hour, computed as the value above divided by total time (hr). Total time for Total = (N1, N2, N3, REM), for REM = (REM), for NREM = (N1, N2, N3). Report as N/A if there are no sleep epochs recorded for the type listed in the column (Total/REM/NREM), or if both leg channels are bad or missing. .. note:: Details for PLM chain computation coming soon. .. list-table:: Table 10. Autoscoring Report Data - ORP :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - Staging - avg_orp - Wake^ - double - Average 30s ORP values across all Wake epochs. * - Staging - avg_orp - NREM^ - double - Average 30s ORP values across all NREM epochs. * - Staging - avg_orp - REM^ - double - Average 30s ORP values across all REM epochs. * - Staging - avg_orp - Sleep^ - double - Average 30s ORP values across all sleep epochs (N1, N2, N3, REM) * - Staging - avg_orp - All Stages^ - double - Average 30s ORP values across all non-bad-EEG epochs. * - Staging - avg_orp - ASO^ - double - Average 30s ORP values across all epochs occuring after sleep onset (computed from lights out, see Sleep Variables table). Bad EEG epochs are refected. * - Summary - Time in Range (min) - double - Number of minutes spent in range specified in column header. Interval is closed on the right side and open on the left (so, 1.25 - 1.50 means 1.25 < x <= 1.50), with the exception of the first interval (0 - 0.25), which is closed on both sides. If a value is (erroneously or not) greater than 2.5, it is added to Decile 9. Values will be N/A if both EEG channels are absent or marked bad. Computed over 30s epochs. * - Summary - Total Study (%) - double - Number computed in previous row, divided by the total minutes across the entire study, including Bad EEG. Values will be N/A if both EEG channels are absent or marked bad. * - Summary - Time in Range (min) - Bad EEG - double - Number of minutes spent with bad EEG. Denoted in code by an ORP value being none. Values will be N/A if both EEG channels are absent or marked bad. * - Summary - Total Study (%) - Bad EEG - double - Number computed in previous row, divided by the total minutes across the entire study, including Bad EEG. Values will be N/A if both EEG channels are absent or marked bad. * - Variables - Sleep Latency - double - tba * - Variables - Sleep Depth - double - tba * - Variables - Sleep Recovery - double - tba .. note:: ^ORP N/A if any of the following hold: a) No epochs in stage wake. b) No ORP data catpured. c) All ORP data is invalid (both left and right side). d) Both EEG channels are bad or missing. .. list-table:: Table 11. Autoscoring Report Data - Heart Rate :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - Maximum - double - Maximum beats per minute measured over the sleep stages listed in the column header. Measurements less than 4 are excluded, as are measurements that fall within a Bad Heart Rate Event. Values will be N/A if both EKG and Pulse channels are absent or marked bad. * - Average - double - Average beats per minute measured over the sleep stages listed in the column header. Measurements less than 4 are excluded, as are measurements that fall within a Bad Heart Rate Event. Values will be N/A if both EKG and Pulse channels are absent or marked bad. * - Minimum - double - Minimum beats per minute measured over the sleep stages listed in the column header. Measurements less than 4 are excluded, as are measurements that fall within a Bad Heart Rate Event. Values will be N/A if both EKG and Pulse channels are absent or marked bad. * - Percent Bad - double - Ratio of bad measurements to total measurements through the duration of the study. Bad measurements include measurements less than 4, and measurements that fall within a Bad Heart Rate Event. Values will be N/A if both EKG and Pulse channels are absent or marked bad. .. list-table:: Table 12. Autoscoring Report Data - Respiratory (by Sleep Stage) :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - - - Number - int - Number of events of type that occur in . Values are N/A if there are no good respiratory channels. Note that only events in sleep (N1, N2, N3, REM) are considered. Events can occur in any body position, including out-of-bed. * - - - Index - double - Event count/hr, where event count is defined above. The total time (denominator) is computed as the sum of the epoch durations labeled with the sleep stage from the column header. Values are N/A if there is no good respiratory data. Example(s) AHI="respiratory"["by_stage"]["apneas_hypopneas"]["index"] RDI="respiratory"["by_stage"]["rdi"]["index"]. * - - Duration - Average - double - The average event duration from the list of events in the count defined above. Values are N/A if there are no good respiratory channels. * - - Duration - Maximum - double - The maximum event duration from the list of events in the count defined above. Values are N/A if there are no good respiratory channels. .. list-table:: Table 13. Autoscoring Report Data - Respiratory (by Body Position) :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - - - Number - int - Number of events of type that occur in . Events are mapped to the last position recorded before or at the event's start time (in simple terms, typically means the stage of the epoch that it starts in). If no position has been recorded, this will be defined as BodyPosition.None. This is not included in total. Values are N/A if there are no good respiratory channels. Events can occur in any body position, including out-of-bed. * - - - Index - double - Event count/hr, where event count is defined above. The total time (denominator) is computed as the sum of the epoch durations labeled with the position from the column header. Values are N/A if there is no good respiratory data. .. list-table:: Table 14. Autoscoring Report Data - Oxygen (saturations) :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - Maximum/Average/Minimum - double - Returns the max/avg/min of all points intersecting with the sleep/position epochs listed in the column header, minus any points that intersect with Bad Oxygen events. In the event that there are no valid points, these three values will be reported as N/A. Values are N/A if the oximeter channel doesn't exist, is marked as bad, or the sample list is empty for that sleep/position label. Only oxygen values between lights off and lights on are considered. * - Time Intervals - double - Return the percentage of samples that oxygen saturation stays within the specified intervals while in the sleep/position epochs listed in the column header, ignoring any values that intersect with Bad Oxygen events. The intervals are as follows (note the open and closed notation): Time > 90 (90,oo), Time 80 - 90 (80,90], Time 70 - 80 (70,80], Time 60 - 70 (60,70], Time 50 - 60 (50,60], Time < 50 (-oo,50). In other words, the left side of the intervals is typically exclusive, and the right side exclusive, with the exception of Time 50 - 60, which are both inclusive. The denominator for the percentage calculation is a summation over all good points (those that don't intersect with Bad Oxygen events). Values are N/A if the oximeter channel doesn't exist, is marked as bad, or the sample list is empty for that sleep/position label. * - Time Bad Signal - double - Returns the percentage of samples that intersect with Bad Oxygen events, across all good and bad oxygen samples. Values are N/A if the oximeter channel doesn't exist, is marked as bad, or the sample list is empty for that sleep/position label. .. list-table:: Table 15. Autoscoring Report Data - Oxygen (desaturations) :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - - Number - int - The number of desaturation events whose level is strictly greater than . Note that the three rows overlap: >2% contains all the events from >3%, plus any others whose level is 3 or less. Returns N/A if the Oximeter channel is bad or missing. Note: values are computed on-the-fly (e.g. the desaturation amounts assigned to the object properties are ignored). The desaturation level is computed as follows: a) Find the minimum oxygen level intersecting with the desaturation event. Oxygen values marked "bad" by Bad Oxygen events are ignored. b) Find the maximum oxygen level intersecting with a window that extends 90 seconds prior to the minimum oxygen level, and extending 30 seconds past the min oxygen level. c) Compute the desaturation as the difference between minimum and maximum. If either minimum or maximum cannot be computed (due to a lack of values), return N/A. * - - Index - double - The number of desaturation events whose level is strictly greater than . Returns N/A if the Oximeter channel is bad or missing. The total time (denominator) is computed as the sum of the epoch durations labeled with the sleep stage/position from the column header. Values are N/A if there is no good respiratory data. .. list-table:: Table 16. Autoscoring Report Data - Oxygen (SPO2 below) :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - - Number - int - The time in seconds below the key value (%). .. list-table:: Table 17. Autoscoring Report Data - Snoring :widths: 20 10 80 :header-rows: 1 :class: tight-table * - Label - Unit - Description * - Snoring Percent - double - The percent of 30 second epochs with four or more snore events. Submitting Scoring Modifications Back to Cerebra ************************************************* POST RPSGT Edits ================= As Cerebra is committed to providing the best **MY Sleep Scoring** results possible we acknowledge that the underlying engine this API is built around must evolve. Users are encouraged to submit JSON data following human review. The JSON structure users submit must match the format that this API (and our **Viewer** application) produces. Cerebra continually monitors the differences between automated and human-reviewed output, which we then use to priortize research effort. Please review details in **File Objects & Pre-Signed URLs** and **File Uploads** for details on how you can contribute to enhancing the study scoring in future releases. .. note:: Coming Soon - Python Code Example for POST call with RPSGT Edits. Label Information and Manufacturer's Notes ******************************************* Manufacturer ============= .. image:: _static/images/factory_symbol.PNG :width: 55 :alt: Factory symbol * Cerebra Medical Ltd. * 1470 Wilson Place, Unit B * Winnipeg, MB, Canada, R3T 3N9 Indications For Use ==================== The Cerebra Sleep System is an integrated diagnostic platform that acquires, transmits, analyzes, and displays physiological signals from adult patients, and then provides for scoring (automatic and manual), editing, and generating reports. The system uses polysomnography (PSG) which records the electroencephalogram (EEG), electrooculogram (EOG), electrocardiogram (ECG), electromyogram (EMG), accelerometry, acoustic signals, nasal airflow, thoracic and abdomen respiratory effort, pulse rate, and oxyhemoglobin saturation, depending on the sleep study configuration. The Cerebra Sleep System is for prescription use in a home or healthcare facility. The Cerebra Sleep System is intended to be used as a support tool by physicians and PSG technologists to aid in the evaluation and diagnosis of sleep disorders. It is intended to provide sleep-related information that is interpreted by a qualified physician to render findings and/or diagnosis, but it does not directly generate a diagnosis. .. raw:: html
Image US federal law restricts this device to sale (or use) on the order of a licensed practitioner.
| Intended Operator ================== The intended operator of this device includes both technicians and users. Use Environment ================ This product is intended for prescription use in a home or a healthcare facility in a designated sleep room. Contraindictions ================= #. User is under the age of 18. #. Cognitive impairment (inability to follow simple instructions) resulting in inability to apply the home sleep testing equipment when another individual is not available to assist with this task. #. Physical impairment resulting in inability to apply the home sleep testing equipment when another individual is not available to assist with this task. Product Identifiers ==================== MY Sleep Scoring, a module of Cerebra Sleep System. For further identifying information about the MY Sleep Scoring API users are referred to the preceeding sections of this document. The release number for the underlying scoring module is describe in Table 5. Each scoring result is tagged with the scoring release number. The current version of the API is available from the `health endpoint `_. Other ****** Billing ======== The details of the billing service are a work in progress. Please contact `Cerebra `_ for further details. Glossary ========= .. glossary:: Level I A study conducted in a lab setting with many channels. Level II A home sleep study with EEG and respiratory channels. Level III A sleep study with respiratory (and related) channels only. EEG Electroencephalogram, a measure of the electrical activity of the brain. Epoch An integer representing a 30-second window of time starting at 1 and going to total EDF duration (seconds) / 30. EOG Electrooculogram, a measure of corneio-retinal standing potential of the eyes. EMG Electromyogram, a measure of electrical activity of muscles. EKG Electrocardiogram, synonymous with ECG, a measure of electrical activity from the heart. NREM Non-REM sleep. Sleep stages N1, N2, or N3. ORP Odds Ratio Product, a patent protected measure of sleep depth that ranges from 2.5 (fully awake) to 0 (deep sleep). PLM Periodic Limb Movement. Study Object An API model that can be thought of as a "container" for one or more sleep studies and scoring run objects. File Object Can take several forms including EDF and JSON. A File Object is created for each EDF (or JSON) submitted to the system. The API will create additional File Objects such as a Resampled EDF and JSON results File Objects. Scoring Run A Scoring Run is an object that includes a list of user settings that can affect scoring, such as the designation of the CO2 unit & the AASM pre-filter condition. Bearer Token A alpha-numeric hash that is used with OAuth 2.0 for authorization. These tokens expire often and should be retrieved on-demand. Refresh Token An alpha-numeric hash that is in a POST request to acquire a Bearer Token. These tokens never expire but can be revoked. Citing Cerebra =============== Written consent? If Cerebra's MY Sleep Scoring API is used for work you publish please use these credentials. .. code-block:: python { "Author": "Cerebra Medical Ltd.", "Organization": "Cerebra Medical Ltd.", "Year": "Refer to release date in MY Sleep Scoring JSON results", "URL": "https://www.cerebra.health" } Release Notes ============== This section will contain change details for future releases. API Guide Revision History =========================== This section will contain change details for the API guide once the first official version is cut. .. toctree:: :maxdepth: 2 :caption: Contents: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search`