Skip to content

Commit

Permalink
BIDS metadata, split recording and API
Browse files Browse the repository at this point in the history
  • Loading branch information
laemtl committed Jun 29, 2021
1 parent fdf3870 commit 8ed8aaa
Show file tree
Hide file tree
Showing 14 changed files with 743 additions and 353 deletions.
6 changes: 3 additions & 3 deletions public/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ const createMainWindow = () => {
preload: path.join(__dirname, 'preload.js'),
nativeWindowOpen: true,
},
width: 900,
width: 1000,
height: 880,
minWidth: 900,
minWidth: 1000,
minHeight: 880,
backgroundColor: '#094580',
});
Expand Down Expand Up @@ -150,7 +150,7 @@ app.on('ready', async () => {
const schema = {
lorisURL: {
type: 'string',
format: 'url',
//format: 'url',
},
};
const store = new Store({schema});
Expand Down
184 changes: 136 additions & 48 deletions python/eeg2bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from eventlet import tpool
import socketio
from python.libs import iEEG
from python.libs.iEEG import ReadError, WriteError, metadata as metadata_fields
from python.libs.Modifier import Modifier
from python.libs import BIDS
from python.libs.loris_api import LorisAPI
import csv
import datetime

# EEG2BIDS Wizard version
appVersion = '1.0.1'
Expand All @@ -21,13 +23,10 @@
# Create socket listener.
sio = socketio.Server(async_mode='eventlet', cors_allowed_origins=[])
app = socketio.WSGIApp(sio)
loris_api = LorisAPI()


# Create Loris API handler.
loris_api = LorisAPI()


@sio.event
def connect(sid, environ):
print('connect: ', sid)
Expand Down Expand Up @@ -62,13 +61,25 @@ def tarfile_bids(sid, data):
def set_loris_credentials(sid, data):
print('set_loris_credentials:', data)
lorisCredentials = data
if lorisCredentials.lorisURL.endswith('/'):
lorisCredentials.lorisURL = lorisCredentials.lorisURL[:-1]
loris_api.url = lorisCredentials.lorisURL + '/api/v0.0.4-dev/'
loris_api.username = lorisCredentials.lorisUsername
loris_api.password = lorisCredentials.lorisPassword
if 'lorisURL' not in lorisCredentials:
print('error with credentials:', data)
return

if lorisCredentials['lorisURL'].endswith('/'):
lorisCredentials['lorisURL'] = lorisCredentials['lorisURL'][:-1]
loris_api.url = lorisCredentials['lorisURL'] + '/api/v0.0.4-dev/'
loris_api.username = lorisCredentials['lorisUsername']
loris_api.password = lorisCredentials['lorisPassword']
loris_api.login()
sio.emit('loris_login_response', {'success': 200})

if loris_api.token:
sio.emit('loris_login_response', {
'success': 200,
'lorisUsername': loris_api.username
})
else:
sio.emit('loris_login_response', {'error': "Can't login to the LORIS instance."})

sio.emit('loris_sites', loris_api.get_sites())
sio.emit('loris_projects', loris_api.get_projects())

Expand All @@ -88,77 +99,154 @@ def get_loris_subprojects(sid, project):


@sio.event
def get_loris_visits(sid, project):
sio.emit('loris_visits', loris_api.get_visits(project))
def get_loris_visits(sid, subproject):
sio.emit('loris_visits', loris_api.get_visits(subproject))


@sio.event
def ieeg_get_header(sid, data):
# data = { file_path: 'path to iEEG file' }
print('ieeg_get_header:', data)
def create_candidate_and_visit(sid, data):
new_candidate = loris_api.create_candidate(
data['project'],
data['dob'],
data['sex'],
data['site'],
)

if new_candidate['CandID']:
loris_api.create_visit(new_candidate['CandID'], data['visit'], data['site'], data['project'], data['subproject'])
loris_api.start_next_stage(new_candidate['CandID'], data['visit'], data['site'], data['subproject'], data['project'], data['date'])
print('new_candidate_created')
sio.emit('new_candidate_created', {'PSCID': new_candidate['PSCID']})

@sio.event
def get_edf_data(sid, data):
# data = { files: 'EDF files (array of {path, name})' }
print('get_edf_data:', data)

if 'files' not in data or not data['files']:
msg = 'No EDF file selected.'
print(msg)
response = {'error': msg}
sio.emit('edf_data', response)
return

headers = []
try:
anonymize = iEEG.Anonymize(data)
header = anonymize.get_header()
for file in data['files']:
anonymize = iEEG.Anonymize(file['path'])
metadata = anonymize.get_header()
year = '20' + str(metadata[0]['year']) if metadata[0]['year'] < 85 else '19' + str(metadata[0]['year'])
date = datetime.datetime(int(year), metadata[0]['month'], metadata[0]['day'], metadata[0]['hour'], metadata[0]['minute'], metadata[0]['second'])

headers.append({
'file': file,
'metadata': metadata,
'date': str(date)
})

for i in range(1, len(headers)):
if set(headers[i-1]['metadata'][1]['ch_names']) != set(headers[i]['metadata'][1]['ch_names']):
msg = 'The files selected contain more than one recording.'
print(msg)
response = {
'error': msg,
}
sio.emit('edf_data', response)
return

# sort the recording per date
headers = sorted(headers, key=lambda k: k['date'])

# return the first split metadata and date
response = {
'header': header[0]
'files': [header['file'] for header in headers],
'subjectID': headers[0]['metadata'][0]['subject_id'],
'recordingID': headers[0]['metadata'][0]['recording_id'],
'date': headers[0]['date']
}
except Exception as ex:
print(ex)

except ReadError as e:
print(e)
response = {
'error': 'Cannot read file - ' + str(e)
}
except Exception as e:
print(e)
response = {
'error': 'Failed to retrieve EDF header information',
}
sio.emit('edf_header', response)
sio.emit('edf_data', response)


@sio.event
def get_metadata(sid, data):
def get_bids_metadata(sid, data):
# data = { file_path: 'path to metadata file' }
print('metadata file:', data)

if not data['file_path']:
print('No file path found.')
response = {
'error': 'No file path found.',
}
else :
print('data:', data)

if 'file_path' not in data or not data['file_path']:
msg = 'No metadata file selected.'
print(msg)
response = {'error': msg}
elif 'modality' not in data or data['modality'] not in ['ieeg', 'eeg']:
msg = 'No valid modality found.'
print(msg)
response = {'error': msg}
else:
try:
with open(data['file_path']) as fd:
reader = csv.DictReader(fd, delimiter="\t", quotechar='"')
response = {
'metadata': {rows['Field']:rows['Value'] for rows in reader}
}
try:
metadata = {rows['Field']:rows['Value'] for rows in reader}
diff = list(set(metadata.keys()) - set(metadata_fields[data['modality']].keys()))
response = {
'metadata': metadata,
'invalid_keys': diff,
}
except KeyError:
metadata = {}
response = {
'error': 'Metadata file format is not valid.',
}
except IOError:
print("Could not read the metadata file.")
msg = "Could not read the metadata file."
print(msg)
response = {
'error': 'No file path found.',
'error': msg,
}

sio.emit('metadata', response)
sio.emit('bids_metadata', response)


def edf_to_bids_thread(data):
print('data is ')
print(data)
error_messages = []
if not data['file_paths']:
if 'edfData' not in data or 'files' not in data['edfData'] or not data['edfData']['files']:
error_messages.append('No .edf file(s) to convert.')
if not data['bids_directory']:
if 'bids_directory' not in data or not data['bids_directory']:
error_messages.append('The BIDS output directory is missing.')
if not data['session']:
error_messages.append('The LORIS Visit Label is missing.')

if not error_messages:
time = iEEG.Time()
data['output_time'] = 'output-' + time.latest_output
iEEG.Converter(data) # EDF to BIDS format.

# store subject_id for Modifier
data['subject_id'] = iEEG.Converter.m_info['subject_id']
data['appVersion'] = appVersion
Modifier(data) # Modifies data of BIDS format
response = {
'output_time': data['output_time']
}
try:
iEEG.Converter(data) # EDF to BIDS format.

# store subject_id for Modifier
data['subject_id'] = iEEG.Converter.m_info['subject_id']
data['appVersion'] = app_version
Modifier(data) # Modifies data of BIDS format
response = {
'output_time': data['output_time']
}
return eventlet.tpool.Proxy(response)
except ReadError as e:
error_messages.append('Cannot read file - ' + str(e))
except WriteError as e:
error_messages.append('Cannot write file - ' + str(e))
else:
response = {
'error': error_messages
Expand All @@ -175,17 +263,17 @@ def edf_to_bids(sid, data):
response = eventlet.tpool.execute(edf_to_bids_thread, data)
print(response)
print('Response received!')
sio.emit('response', response.copy())
sio.emit('bids', response.copy())


@sio.event
def validate_bids(sid, data):
# data = { bids_directory: '../path/to/bids/output', output_time: 'bids output time' }
print('validate_bids: ', data)
error_messages = []
if not data['bids_directory']:
if 'bids_directory' not in data or not data['bids_directory']:
error_messages.append('The BIDS output directory is missing.')
if not data['output_time']:
if 'output_time' not in data or not data['output_time']:
error_messages.append('The BIDS format is missing.')
if not error_messages:
BIDS.Validate(data)
Expand Down
4 changes: 2 additions & 2 deletions python/libs/BIDS.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ def __init__(self, data):

if filename.endswith('_annotations.tsv'):
continue

if filename.endswith('_annotations.json'):
continue

temp = os.path.join(path, filename)
file_paths.append(temp[len(start_path):len(temp)])
result.append(validator.is_bids(temp[len(start_path):len(temp)]))
Expand Down
Loading

0 comments on commit 8ed8aaa

Please sign in to comment.