Cerebra - MY Sleep Scoring API Guide

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 .

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.

headers = {"Authorization": f"Bearer {bearer_token}"}

Role-based Access

The API incorporates role-based access logic with accounts given one of three types:

  1. User

  2. SiteAdmin

  3. 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:

  1. POST to receive your Bearer token (/api/token).

  2. GET a list of files for the current user (/api/file).

  3. POST to create a study object (/api/study/).

  4. POST to create a file object and scoringrun associated with the study (/api/file).

  5. POST a channel mapping dictionary (/api/channelmapping/).

  6. GET the list of channel mappings to review names or indexes (/api/channelmapping).

  7. PATCH the channel mapping to the file object created in step 4 (/api/file/{file_id}).

  8. POST the EDF file to the pre-signed URL provided from step 3 & 7 (pre-signed URL)

  9. Pour yourself a coffee and wait for the Cerebra infrastructure to score the file (~5 minutes).

  10. GET a list of files for the study (/api/file).

  11. 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.

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.

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).

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.

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.

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.

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.

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.

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.

  1. description

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).

Table 1. Channel Requirements by Type for EDF Files

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.

Table 2. Channel Mapping Key Names and Type

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.

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).

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.

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.

{
'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.

Table 3. Run settings on Scoringrun Objects

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.

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:

  1. Raw EDF

  2. Resampled EDF

  3. Autoscoring Events

  4. Reporting Data

  5. 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:

  1. name

  2. description

  3. study_id

  4. 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.

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.

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.

Table 4. File Status Values

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.

{
  "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.

Table 5. Autoscoring Results Dictionary Key

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.

{
  "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.

Table 6. Autoscoring Report Data - Sleep Variables

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.

Table 7. Autoscoring Report Data - Stage Time

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.

Table 8. Autoscoring Report Data - Arousals

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).

Table 9. Autoscoring Report Data - Legs

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.

Table 10. Autoscoring Report Data - ORP

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.

Table 11. Autoscoring Report Data - Heart Rate

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.

Table 12. Autoscoring Report Data - Respiratory (by Sleep Stage)

Label

Unit

Description

<Respiratory Type> - <SleepStage> - Number

int

Number of events of type <Respiratory Type> that occur in <Sleep Stage>. 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.

<Respiratory Type> - <SleepStage> - 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”].

<Respiratory Type> - 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.

<Respiratory Type> - 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.

Table 13. Autoscoring Report Data - Respiratory (by Body Position)

Label

Unit

Description

<Respiratory Type> - <Body Position> - Number

int

Number of events of type <Respiratory Type> that occur in <Body Position>. 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.

<Respiratory Type> - <Body Position> - 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.

Table 14. Autoscoring Report Data - Oxygen (saturations)

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.

Table 15. Autoscoring Report Data - Oxygen (desaturations)

Label

Unit

Description

<percentage> - Number

int

The number of desaturation events whose level is strictly greater than <percentage>. 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.

<percentage> - Index

double

The number of desaturation events whose level is strictly greater than <percentage>. 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.

Table 16. Autoscoring Report Data - Oxygen (SPO2 below)

Label

Unit

Description

<percentage> - Number

int

The time in seconds below the key value (%).

Table 17. Autoscoring Report Data - Snoring

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

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.

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

  1. User is under the age of 18.

  2. 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.

  3. 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

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.

{
  "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.

Indices and tables