Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 84 additions & 55 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,86 @@
# Python Example

## Requirement
- This example works with Python >= 3.7
- Install websocket client via `pip install websocket-client`
- Install python-dispatch via `pip install python-dispatch`

## Before you start

To run the existing example you will need to do a few things.

1. You will need an EMOTIV headset. You can purchase a headset in our [online
store](https://www.emotiv.com/)
2. Next, [download and install](https://www.emotiv.com/developer/) the Cortex
service. Please note that currently, the Cortex service is only available
for Windows and macOS.
3. We have updated our Terms of Use, Privacy Policy and EULA to comply with
GDPR. Please login via the EMOTIV Launcher to read and accept our latest policies
in order to proceed using the following examples.
4. Next, to get a client id and a client secret, you must connect to your
Emotiv account on
[emotiv.com](https://www.emotiv.com/my-account/cortex-apps/) and create a
Cortex app. If you don't have a EmotivID, you can [register
here](https://id.emotivcloud.com/eoidc/account/registration/).
5. Then, if you have not already, you will need to login with your Emotiv id in
the EMOTIV Launcher.
6. Finally, the first time you run these examples, you also need to authorize
them in the EMOTIV Launcher.

This code is purely an example of how to work with Cortex. We strongly
recommend adjusting the code to your purposes.

## Cortex Library
- [`cortex.py`](./cortex.py) - the wrapper lib around EMOTIV Cortex API.

## Susbcribe Data
- [`sub_data.py`](./sub_data.py) shows data streaming from Cortex: EEG, motion, band power and Performance Metrics.
- For more details https://emotiv.gitbook.io/cortex-api/data-subscription

## BCI
- [`mental_command_train.py`](./mental_command_train.py) shows Mental Command training.
- [`facial_expression_train.py`](./facial_expression_train.py) shows facial expression training.
- For more details https://emotiv.gitbook.io/cortex-api/bci

## Advanced BCI
- [`live_advance.py`](./live_advance.py) shows the ability to get and set sensitivity of mental command action in live mode.
- For more details https://emotiv.gitbook.io/cortex-api/advanced-bci

## Create record and export to file
- [`record.py`](./record.py) shows how to create record and export data to CSV or EDF format.
- For more details https://emotiv.gitbook.io/cortex-api/records

## Inject marker while recording
- [`marker.py`](./marker.py) shows how to inject marker during a recording.
- For more details https://emotiv.gitbook.io/cortex-api/markers

# Emotiv Cortex API Python Examples

This repository provides a set of Python examples to help you get started with the [Emotiv Cortex API](https://emotiv.gitbook.io/cortex-api). Each script demonstrates a specific workflow, making it easier to understand and integrate Cortex API features into your own projects.


## Requirements

- Python 2.7+ or Python 3.4+
- Install dependencies:
- `pip install websocket-client`
- `pip install python-dispatch`


## Getting Started

Before running the examples, please ensure you have completed the following steps:

1. **Download and Install EMOTIV Launcher**: Download from [here](https://www.emotiv.com/products/emotiv-launcher). Log in with your Emotiv ID and accept the latest Terms of Use, Privacy Policy, and EULA in the Launcher.
2. **Accept Policies**: If prompted, accept any additional policies in the EMOTIV Launcher.
3. **Obtain an EMOTIV Headset or Create a Virtual Device**:
- Purchase a headset from the [EMOTIV online store](https://www.emotiv.com/), **or**
- Use a virtual headset in the EMOTIV Launcher by following [these instructions](https://emotiv.gitbook.io/emotiv-launcher/devices-setting-up-virtual-brainwear-r/creating-a-virtual-brainwear-device).
4. **Get Client ID & Secret**: Log in to your Emotiv account at [emotiv.com](https://www.emotiv.com/my-account/cortex-apps/) and create a Cortex app. [Register here](https://id.emotivcloud.com/eoidc/account/registration/) if you don't have an account.
5. **Authorize Examples**: The first time you run these examples, you may need to grant permission for your application to work with Emotiv Cortex.

---

## Example Scripts Overview

### 1. `cortex.py` — Cortex API Wrapper
Central wrapper class for the Cortex API. Handles:
- Opening and managing the websocket connection
- Buidling JSON-RPC requests
- Handling responses, errors, and emitting events to corresponding classes
- Parsing and dispatching data to workflow scripts

### 2. `sub_data.py` — Subscribe to Data Streams
Demonstrates how to:
- Subscribe to data streams (EEG, motion, performance metrics, etc.)
- Print or process incoming data
See: [Data Subscription](https://emotiv.gitbook.io/cortex-api/data-subscription)

### 3. `record.py` — Record and Export Data
Demonstrates how to:
- Create a new record
- Stop a record
- Export recorded data to CSV or EDF
See: [Records](https://emotiv.gitbook.io/cortex-api/records)

### 4. `marker.py` — Inject Markers
Demonstrates how to:
- Inject markers into a record during data collection
- Export records with marker information
See: [Markers](https://emotiv.gitbook.io/cortex-api/markers)


### 5. `mental_command_train.py` — Mental Command Training
Demonstrates how to:
- Load or create a training profile
- Train mental command actions (e.g., neutral, push, pull)
See: [BCI](https://emotiv.gitbook.io/cortex-api/bci)


### 6. `facial_expression_train.py` — Facial Expression Training
Demonstrates how to:
- Load or create a training profile
- Train facial expression actions (e.g., neutral, surprise, smile)
See: [BCI](https://emotiv.gitbook.io/cortex-api/bci)

### 7. `live_advance.py` — Advanced Live Data & Sensitivity
Demonstrates how to:
- Load a trained profile
- Subscribe to the 'com' stream for live mental command data
- (Optionally) Subscribe to the 'fac' stream for live facial expression data
- Get and set sensitivity for mental command actions in live mode
See: [Advanced BCI](https://emotiv.gitbook.io/cortex-api/advanced-bci)

---

## Tips
- Each script is self-contained and demonstrates a specific workflow.
- Adjust the code as needed for your own applications.
- For more details, refer to the [official Cortex API documentation](https://emotiv.gitbook.io/cortex-api/).


52 changes: 35 additions & 17 deletions python/cortex.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import websocket #'pip install websocket-client' for install
from datetime import datetime
import json
import ssl
import time
import sys
from pydispatch import Dispatcher
import warnings
import threading
# --- BEGIN: Simplified environment checks ---
# 1. Check Python version
if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 4):
print(f"[ERROR] Python 2.7+ or 3.4+ is required. You are using Python {sys.version_info.major}.{sys.version_info.minor}.", file=sys.stderr)
sys.exit(1)

# 2. Check websocket-client
try:
import websocket
except ImportError:
print(f"[ERROR] Required library 'websocket-client' is not installed. Please run: {sys.executable} -m pip install websocket-client", file=sys.stderr)
sys.exit(1)

# 3. Check python-dispatch
try:
from pydispatch import Dispatcher # needed for class inheritance
except ImportError:
print(f"[ERROR] Required library 'python-dispatch' is not installed. Please run: {sys.executable} -m pip install python-dispatch", file=sys.stderr)
sys.exit(1)
# --- END: Simplified environment checks ---

import threading
import ssl
import time
import json
from datetime import datetime

# define request id
QUERY_HEADSET_ID = 1
Expand Down Expand Up @@ -92,7 +110,7 @@ def __init__(self, client_id, client_secret, debug_mode=False, **kwargs):
if key == 'license':
self.license = value
elif key == 'debit':
self.debit == value
self.debit = value
elif key == 'headset_id':
self.headset_id = value

Expand All @@ -104,24 +122,24 @@ def open(self):
on_open = self.on_open,
on_error=self.on_error,
on_close=self.on_close)
threadName = "WebsockThread:-{:%Y%m%d%H%M%S}".format(datetime.utcnow())
thread_name = "WebsockThread:-{:%Y%m%d%H%M%S}".format(datetime.now())

# As default, a Emotiv self-signed certificate is required.
# If you don't want to use the certificate, please replace by the below line by sslopt={"cert_reqs": ssl.CERT_NONE}
sslopt = {'ca_certs': "../certificates/rootCA.pem", "cert_reqs": ssl.CERT_REQUIRED}

self.websock_thread = threading.Thread(target=self.ws.run_forever, args=(None, sslopt), name=threadName)
self.websock_thread = threading.Thread(target=self.ws.run_forever, args=(None, sslopt), name=thread_name)
self.websock_thread .start()
self.websock_thread.join()

def close(self):
self.ws.close()

def set_wanted_headset(self, headsetId):
self.headset_id = headsetId
def set_wanted_headset(self, headset_id):
self.headset_id = headset_id

def set_wanted_profile(self, profileName):
self.profile_name = profileName
def set_wanted_profile(self, profile_name):
self.profile_name = profile_name

def on_open(self, *args, **kwargs):
print("websocket opened")
Expand Down Expand Up @@ -305,7 +323,7 @@ def handle_result(self, recv_dic):
self.emit('export_record_done', data=success_export)
elif req_id == INJECT_MARKER_REQUEST_ID:
self.emit('inject_marker_done', data=result_dic['marker'])
elif req_id == INJECT_MARKER_REQUEST_ID:
elif req_id == UPDATE_MARKER_REQUEST_ID:
self.emit('update_marker_done', data=result_dic['marker'])
else:
print('No handling for response of request ' + str(req_id))
Expand Down Expand Up @@ -784,11 +802,11 @@ def inject_marker_request(self, time, value, label, **kwargs):
print('inject marker request \n', json.dumps(inject_marker_request, indent=4))
self.ws.send(json.dumps(inject_marker_request))

def update_marker_request(self, markerId, time, **kwargs):
def update_marker_request(self, marker_id, time, **kwargs):
print('update marker --------------------------------')
params_val = {"cortexToken": self.auth,
"session": self.session_id,
"markerId": markerId,
"markerId": marker_id,
"time": time}

for key, value in kwargs.items():
Expand Down
10 changes: 5 additions & 5 deletions python/live_advance.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(self, app_client_id, app_client_secret, **kwargs):
self.c.bind(mc_action_sensitivity_done=self.on_mc_action_sensitivity_done)
self.c.bind(inform_error=self.on_inform_error)

def start(self, profile_name, headsetId=''):
def start(self, profile_name, headset_id=''):
"""
To start live process as below workflow
(1) check access right -> authorize -> connect headset->create session
Expand All @@ -48,9 +48,9 @@ def start(self, profile_name, headsetId=''):
----------
profile_name : string, required
name of profile
headsetId: string , optional
headset_id: string , optional
id of wanted headet which you want to work with it.
If the headsetId is empty, the first headset in list will be set as wanted headset
If the headset_id is empty, the first headset in list will be set as wanted headset
Returns
-------
None
Expand All @@ -61,8 +61,8 @@ def start(self, profile_name, headsetId=''):
self.profile_name = profile_name
self.c.set_wanted_profile(profile_name)

if headsetId != '':
self.c.set_wanted_headset(headsetId)
if headset_id != '':
self.c.set_wanted_headset(headset_id)

self.c.open()

Expand Down
38 changes: 17 additions & 21 deletions python/marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ def __init__(self, app_client_id, app_client_secret, **kwargs):
self.c.bind(create_session_done=self.on_create_session_done)
self.c.bind(create_record_done=self.on_create_record_done)
self.c.bind(stop_record_done=self.on_stop_record_done)
self.c.bind(warn_cortex_stop_all_sub=self.on_warn_cortex_stop_all_sub)
self.c.bind(inject_marker_done=self.on_inject_marker_done)
self.c.bind(export_record_done=self.on_export_record_done)
self.c.bind(inform_error=self.on_inform_error)
self.c.bind(warn_record_post_processing_done=self.on_warn_record_post_processing_done)

def start(self, number_markers=10, headsetId=''):
def start(self, number_markers=10, headset_id=''):
"""
To start data recording and inject marker process as below workflow
(1) check access right -> authorize -> connect headset->create session
Expand All @@ -23,18 +23,18 @@ def start(self, number_markers=10, headsetId=''):
number_markers: int, required
number of markers

headsetId: string , optional
headset_id: string , optional
id of wanted headet which you want to work with it.
If the headsetId is empty, the first headset in list will be set as wanted headset
If the headset_id is empty, the first headset in list will be set as wanted headset
Returns
-------
None
"""
self.number_markers = number_markers
self.marker_idx = 0

if headsetId != '':
self.c.set_wanted_headset(headsetId)
if headset_id != '':
self.c.set_wanted_headset(headset_id)

self.c.open()

Expand Down Expand Up @@ -96,7 +96,7 @@ def inject_marker(self, time, value, label, **kwargs):
"""
self.c.inject_marker_request(time, value, label, **kwargs)

def update_marker(self, markerId, time, **kwargs):
def update_marker(self, marker_id, time, **kwargs):
"""
To update a marker that was previously created by inject_marker
Parameters
Expand All @@ -106,7 +106,7 @@ def update_marker(self, markerId, time, **kwargs):
-------
None
"""
self.c.update_marker_request(markerId, time, **kwargs)
self.c.update_marker_request(marker_id, time, **kwargs)

# callbacks functions
def on_create_session_done(self, *args, **kwargs):
Expand Down Expand Up @@ -136,12 +136,7 @@ def on_stop_record_done(self, *args, **kwargs):
title = data['title']
print('on_stop_record_done: recordId: {0}, title: {1}, startTime: {2}, endTime: {3}'.format(record_id, title, start_time, end_time))

# disconnect headset to export record
print('on_stop_record_done: Disconnect the headset to export record')
self.c.disconnect_headset()

def on_inject_marker_done(self, *args, **kwargs):

data = kwargs.get('data')
marker_id = data['uuid']
start_time = data['startDatetime']
Expand All @@ -153,14 +148,6 @@ def on_inject_marker_done(self, *args, **kwargs):
# stop record
self.stop_record()

def on_warn_cortex_stop_all_sub(self, *args, **kwargs):
print('on_warn_cortex_stop_all_sub')
# cortex has closed session. Wait some seconds before exporting record
time.sleep(3)

self.export_record(self.record_export_folder, self.record_export_data_types,
self.record_export_format, [self.record_id], self.record_export_version)

def on_export_record_done(self, *args, **kwargs):
print('on_export_record_done')
data = kwargs.get('data')
Expand All @@ -170,6 +157,15 @@ def on_export_record_done(self, *args, **kwargs):
def on_inform_error(self, *args, **kwargs):
error_data = kwargs.get('error_data')
print(error_data)

def on_warn_record_post_processing_done(self, *args, **kwargs):
record_id = kwargs.get('data')
print('on_warn_record_post_processing_done: The record ', record_id, 'has been post-processed. Now, you can export the record')

# you must stop the record before you can export it.
# if you want to export a record immediately after you stop it then you must wait for the warning 30 before you try to export.
self.export_record(self.record_export_folder, self.record_export_data_types,
self.record_export_format, [record_id], self.record_export_version)


# -----------------------------------------------------------
Expand Down
Loading
Loading