Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update query records and request to download api #204

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ recommend adjusting the code to your purposes.
- [`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

## Query, download and export records
- [`query_records.py`](./query_records.py) shows how to query record, download records and export record 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
Expand Down
66 changes: 61 additions & 5 deletions python/cortex.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pydispatch import Dispatcher
import warnings
import threading
import os


# define request id
Expand Down Expand Up @@ -34,6 +35,8 @@
GET_CORTEX_INFO_ID = 22
UPDATE_MARKER_REQUEST_ID = 23
UNSUB_REQUEST_ID = 24
QUERY_RECORDS_ID = 25
REQUEST_DOWNLOAD_RECORDS_ID = 26

#define error_code
ERR_PROFILE_ACCESS_DENIED = -32046
Expand All @@ -60,9 +63,9 @@

class Cortex(Dispatcher):

_events_ = ['inform_error','create_session_done', 'query_profile_done', 'load_unload_profile_done',
_events_ = ['inform_error', 'authorize_done', 'create_session_done', 'query_profile_done', 'load_unload_profile_done',
'save_profile_done', 'get_mc_active_action_done','mc_brainmap_done', 'mc_action_sensitivity_done',
'mc_training_threshold_done', 'create_record_done', 'stop_record_done','warn_cortex_stop_all_sub',
'mc_training_threshold_done', 'create_record_done','query_records_done', 'request_download_records_done', 'stop_record_done','warn_cortex_stop_all_sub',
'inject_marker_done', 'update_marker_done', 'export_record_done', 'new_data_labels',
'new_com_data', 'new_fe_data', 'new_eeg_data', 'new_mot_data', 'new_dev_data',
'new_met_data', 'new_pow_data', 'new_sys_data']
Expand All @@ -73,6 +76,7 @@ def __init__(self, client_id, client_secret, debug_mode=False, **kwargs):
self.debug = debug_mode
self.debit = 10
self.license = ''
self.auto_create_session = True

if client_id == '':
raise ValueError('Empty your_app_client_id. Please fill in your_app_client_id before running the example.')
Expand All @@ -92,6 +96,8 @@ def __init__(self, client_id, client_secret, debug_mode=False, **kwargs):
self.debit == value
elif key == 'headset_id':
self.headset_id = value
elif key == 'auto_create_session':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should tell about optional params in readme file..

self.auto_create_session = value

def open(self):
url = "wss://localhost:6868"
Expand All @@ -105,7 +111,11 @@ def open(self):

# 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}

file_dir_path = os.path.dirname(os.path.realpath(__file__))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use high level package to handle file path
https://docs.python.org/3/library/pathlib.html

parent_dir_path = os.path.abspath(os.path.join(file_dir_path, os.pardir))
certificate_path = os.path.join(parent_dir_path,'certificates', 'rootCA.pem')
sslopt = {'ca_certs': certificate_path, "cert_reqs": ssl.CERT_REQUIRED}

self.websock_thread = threading.Thread(target=self.ws.run_forever, args=(None, sslopt), name=threadName)
self.websock_thread .start()
Expand Down Expand Up @@ -160,8 +170,11 @@ def handle_result(self, recv_dic):
elif req_id == AUTHORIZE_ID:
print("Authorize successfully.")
self.auth = result_dic['cortexToken']
# query headsets
self.query_headset()
if self.auto_create_session:
# query headsets
self.query_headset()
else:
self.emit('authorize_done')
elif req_id == QUERY_HEADSET_ID:
self.headset_list = result_dic
found_headset = False
Expand Down Expand Up @@ -272,6 +285,14 @@ def handle_result(self, recv_dic):
self.emit('mc_brainmap_done', data=result_dic)
elif req_id == SENSITIVITY_REQUEST_ID:
self.emit('mc_action_sensitivity_done', data=result_dic)
elif req_id == QUERY_RECORDS_ID:
Copy link
Contributor

@nguoithichkhampha nguoithichkhampha Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't use switch case. it's more readable then a lot of if-else.
you can write
match req_id: case QUERY_RECORDS_ID:

record_num = result_dic['count']
limit_num = result_dic['limit']
offset_num = result_dic['offset']
records = result_dic['records']
self.emit('query_records_done', data=records, count=record_num, limit=limit_num, offset=offset_num)
elif req_id == REQUEST_DOWNLOAD_RECORDS_ID:
self.emit('request_download_records_done', data=result_dic)
elif req_id == CREATE_RECORD_REQUEST_ID:
self.record_id = result_dic['record']['uuid']
self.emit('create_record_done', data=result_dic['record'])
Expand Down Expand Up @@ -666,6 +687,41 @@ def train_request(self, detection, action, status):

self.ws.send(json.dumps(train_request_json))

def query_records(self, query_params):
print('query records --------------------------------')

params_val = {"cortexToken": self.auth}

for key, value in query_params.items():
params_val.update({key: value})

query_records_request = {
"jsonrpc": "2.0",
"method": "queryRecords",
"params": params_val,
"id": QUERY_RECORDS_ID
}
if self.debug:
print('query records request:\n', json.dumps(query_records_request, indent=4))

self.ws.send(json.dumps(query_records_request))

def request_download_records(self, recordIds):
print('request to download records --------------------------------')

params_val = {"cortexToken": self.auth, 'recordIds': recordIds}

download_records_request = {
"jsonrpc": "2.0",
"method": "requestToDownloadRecordData",
"params": params_val,
"id": REQUEST_DOWNLOAD_RECORDS_ID
}
if self.debug:
print('requestToDownloadRecordData request:\n', json.dumps(download_records_request, indent=4))

self.ws.send(json.dumps(download_records_request))

def create_record(self, title, **kwargs):
print('create record --------------------------------')

Expand Down
203 changes: 203 additions & 0 deletions python/query_records.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
from cortex import Cortex

class Records():
"""
A class to query records, request to download undownloaded records, and export local records

Attributes
----------
c : Cortex
Cortex communicate with Emotiv Cortex Service

Methods
-------
start():
start authorize process.
query_records():
To query records owned by the current user
request_download_records()
To request to download records if they are not at local machine
export_record()
To export records to CSV/ EDF files
"""
def __init__(self, app_client_id, app_client_secret, **kwargs):
"""
Constructs cortex client and bind a function to query records, request to download and export records
If you do not want to log request and response message , set debug_mode = False. The default is True
"""
print("Query Records __init__")
self.c = Cortex(app_client_id, app_client_secret, debug_mode=True, **kwargs)
self.c.bind(authorize_done=self.on_authorize_done)
self.c.bind(query_records_done=self.on_query_records_done)
self.c.bind(export_record_done=self.on_export_record_done)
self.c.bind(request_download_records_done=self.on_request_download_records_done)
self.c.bind(inform_error=self.on_inform_error)

def start(self):
"""
To open websocket and process authorizing step.
After get authorize_done The program will query records
Returns
-------
None
"""
self.c.open()

def query_records(self, license_id, application_id):
"""
To query records
Parameters
----------
orderBy : array of object, required : Specify how to sort the records.
query: object, required: An object to filter the records.
If you set an empty object, it will return all records created by your application
If you want get records created by other application, you need set licenseId and applicationId as parameter of query object
More detail at https://emotiv.gitbook.io/cortex-api/records/queryrecords
Returns
-------
"""

query_obj = {}
if license_id != '':
query_obj["licenseId"] = license_id
if application_id != '':
query_obj["applicationId"] = application_id

query_params = {
"orderBy": [{ "startDatetime": "DESC" }],
"query": query_obj,
"includeSyncStatusInfo":True
}
self.c.query_records(query_params)

def request_download_records(self, record_ids):
"""
To request to download records
Parameters
----------
record_ids : list, required: list of wanted record ids
More detail at https://emotiv.gitbook.io/cortex-api/records/requesttodownloadrecorddata
Returns
-------
None
"""
self.c.request_download_records(record_ids)

def export_record(self, record_ids, license_ids):
"""
To export records
By default, you can only export the records that were created by your application.
If you want to export a record that was created by another applications
then you must provide the license ids of those applications in the parameter licenseIds.
Parameters
record_ids: list, required: list of wanted export record ids
license_ids: list, no required: list of license id of other applications
----------
More detail at https://emotiv.gitbook.io/cortex-api/records/exportrecord
Returns
-------
None
"""
folder = '' # your place to export, you should have write permission, for example: 'C:\\Users\\NTT\\Desktop'
stream_types = ['EEG', 'MOTION', 'PM', 'BP']
export_format = 'CSV' # support 'CSV' or 'EDF'
version = 'V2'
self.c.export_record(folder, stream_types, export_format, record_ids, version, licenseIds=license_ids)


# callbacks functions
def on_authorize_done(self, *args, **kwargs):
print('on_authorize_done')
# query records
self.query_records(self.license_id, self.application_id)

# callbacks functions
def on_query_records_done(self, *args, **kwargs):
data = kwargs.get('data')
count = kwargs.get('count')
print('on_query_records_done: total records are {0}'.format(count))
# print(data)
not_downloaded_record_Ids = []
export_record_Ids = []
license_ids = []
for item in data:
uuid = item['uuid']
sync_status = item["syncStatus"]["status"]
application_id = item["applicationId"]
license_id = item["licenseId"]
print("recordId {0}, applicationId {1}, sync status {2}".format(uuid, application_id, sync_status))
if (sync_status == "notDownloaded") :
not_downloaded_record_Ids.append(uuid)
elif (sync_status == "neverUploaded") or (sync_status == "downloaded"):
export_record_Ids.append(uuid)
if license_id not in license_ids:
license_ids.append(license_id)

# download records has not downloaded to local machine
if len(not_downloaded_record_Ids) > 0:
self.request_download_records(not_downloaded_record_Ids)

# Open comment below to export records
# if len(export_record_Ids) > 0: # or export records are in local machine
# self.export_record(export_record_Ids, license_ids)

def on_export_record_done(self, *args, **kwargs):
print('on_export_record_done: the successful record exporting as below:')
data = kwargs.get('data')
print(data)
self.c.close()

def on_request_download_records_done(self, *args, **kwargs):
data = kwargs.get('data')
success_records = []
for item in data['success']:
record_Id = item['recordId']
print('The record '+ record_Id + ' is downloaded successfully.')
success_records.append(record_Id)

for item in data['failure']:
record_Id = item['recordId']
failed_msg = item['message']
print('The record '+ record_Id + ' is downloaded unsuccessfully. Because: ' + failed_msg)

self.c.close()

def on_inform_error(self, *args, **kwargs):
error_data = kwargs.get('error_data')
print(error_data)

# -----------------------------------------------------------
#
# GETTING STARTED
# - Please reference to https://emotiv.gitbook.io/cortex-api/ first.
# - Please make sure the your_app_client_id and your_app_client_secret are set before starting running.
# RESULT
# - on_query_records_done: will show all records filtered by query condition in query_record
# - on_export_record_done: will show the successful exported record
# - on_request_download_records_done: will show all success and failure download case
#
# -----------------------------------------------------------

def main():

# Please fill your application clientId and clientSecret before running script
your_app_client_id = ''
your_app_client_secret = ''

# Don't need to create session in this case
r = Records(your_app_client_id, your_app_client_secret, auto_create_session= False)

# As default, the Program will query records of your application.
# In the case, you want to query records created from other application (such as EmotivPRO).
# You need set license_id and application_id of the application
# If set license_id without application_id. It will return records created from all applications use the license_id
# If set both license_id and application_id. It will return records from the application has the application_id
r.license_id = ''
r.application_id = ''

r.start()

if __name__ =='__main__':
main()

# -----------------------------------------------------------