diff --git a/public/electron.js b/public/electron.js
index b9ece88..76ac0d9 100644
--- a/public/electron.js
+++ b/public/electron.js
@@ -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',
});
@@ -150,7 +150,7 @@ app.on('ready', async () => {
const schema = {
lorisURL: {
type: 'string',
- format: 'url',
+ //format: 'url',
},
};
const store = new Store({schema});
diff --git a/python/eeg2bids.py b/python/eeg2bids.py
index ae58eeb..978c214 100644
--- a/python/eeg2bids.py
+++ b/python/eeg2bids.py
@@ -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'
@@ -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)
@@ -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())
@@ -88,61 +99,131 @@ 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.')
@@ -150,15 +231,22 @@ def edf_to_bids_thread(data):
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
@@ -175,7 +263,7 @@ 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
@@ -183,9 +271,9 @@ 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)
diff --git a/python/libs/BIDS.py b/python/libs/BIDS.py
index b6f6f0b..a47b21e 100644
--- a/python/libs/BIDS.py
+++ b/python/libs/BIDS.py
@@ -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)]))
diff --git a/python/libs/Modifier.py b/python/libs/Modifier.py
index 267faf6..9711d70 100644
--- a/python/libs/Modifier.py
+++ b/python/libs/Modifier.py
@@ -3,6 +3,7 @@
import json
import re
import shutil
+from python.libs.iEEG import metadata as metadata_fields
class Modifier:
def __init__(self, data):
@@ -14,6 +15,7 @@ def __init__(self, data):
self.modify_dataset_description_json()
self.modify_participants_tsv()
self.modify_participants_json()
+ self.clean_dataset_files()
self.copy_events_tsv()
self.copy_annotations_files()
self.modify_eeg_json()
@@ -27,7 +29,7 @@ def get_bids_root_path(self):
def get_eeg_path(self):
directory_path = 'sub-' + self.data['participantID'].replace('_', '').replace('-', '').replace(' ', '')
-
+
return os.path.join(
self.get_bids_root_path(),
directory_path,
@@ -35,6 +37,36 @@ def get_eeg_path(self):
self.data['modality']
)
+
+ def clean_dataset_files(self):
+ if len(self.data['edfData']['files']) > 0:
+ # for split recording, clean the duplicates _eeg.json and _channels.tsv
+ channels_files = [f for f in os.listdir(self.get_eeg_path()) if f.endswith('_channels.tsv')]
+ for i in range(1, len(channels_files)):
+ filename = os.path.join(self.get_eeg_path(), channels_files[i])
+ os.remove(filename)
+
+ sidecar_files = [f for f in os.listdir(self.get_eeg_path()) if f.endswith('eeg.json')]
+ for i in range(1, len(sidecar_files)):
+ filename = os.path.join(self.get_eeg_path(), sidecar_files[i])
+ os.remove(filename)
+
+ # remove the split suffix in the file names
+ fileOrig = os.path.join(self.get_eeg_path(), channels_files[0])
+ fileDest = os.path.join(
+ self.get_eeg_path(),
+ re.sub(r"_split-[0-9]+", '', channels_files[0])
+ )
+ os.rename(fileOrig, fileDest)
+
+ fileOrig = os.path.join(self.get_eeg_path(), sidecar_files[0])
+ fileDest = os.path.join(
+ self.get_eeg_path(),
+ re.sub(r"_split-[0-9]+", '', sidecar_files[0])
+ )
+ os.rename(fileOrig, fileDest)
+
+
def modify_dataset_description_json(self):
file_path = os.path.join(
self.get_bids_root_path(),
@@ -52,7 +84,7 @@ def modify_dataset_description_json(self):
except IOError:
print("Could not read or write dataset_description.json file")
-
+
def modify_participants_tsv(self):
file_path = os.path.join(
self.get_bids_root_path(),
@@ -137,7 +169,7 @@ def modify_participants_json(self):
def copy_annotations_files(self):
if not self.data['annotations_tsv'] and not self.data['annotations_json']:
return
-
+
file = os.path.join(
self.get_bids_root_path(),
'.bidsignore'
@@ -150,7 +182,7 @@ def copy_annotations_files(self):
edf_file = [f for f in os.listdir(self.get_eeg_path()) if f.endswith('.edf')]
filename = os.path.join(self.get_eeg_path(), re.sub(r"_i?eeg.edf", '_annotations', edf_file[0]))
-
+
if self.data['annotations_tsv']:
shutil.copyfile(
self.data['annotations_tsv'],
@@ -163,11 +195,11 @@ def copy_annotations_files(self):
os.path.join(self.get_eeg_path(), filename + '.json')
)
-
+
def copy_events_tsv(self):
if not self.data['events_tsv']:
return
-
+
# events.tsv data collected:
output = []
@@ -246,17 +278,21 @@ def modify_eeg_json(self):
raise ValueError('Found more than one eeg.json file')
file_path = os.path.join(self.get_eeg_path(), eeg_json[0])
-
+
try:
with open(file_path, "r") as fp:
file_data = json.load(fp)
file_data["SoftwareFilters"] = self.data['software_filters']
file_data["RecordingType"] = self.data['recording_type']
-
- if (self.data["modality"] == "ieeg"):
- file_data["iEEGReference"] = self.data['reference']
- else:
- file_data["EEGReference"] = self.data['reference']
+
+ referenceField = metadata_fields[self.data["modality"]]['Reference']
+ file_data[referenceField] = self.data['reference']
+
+ if 'metadata' in self.data['bidsMetadata'] and 'invalid_keys' in self.data['bidsMetadata']:
+ for key in self.data['bidsMetadata']['metadata']:
+ if key not in self.data['bidsMetadata']['invalid_keys']:
+ fieldName = metadata_fields[self.data["modality"]][key]
+ file_data[fieldName] = self.data['bidsMetadata']['metadata'][key]
with open(file_path, "w") as fp:
json.dump(file_data, fp, indent=4)
diff --git a/python/libs/iEEG.py b/python/libs/iEEG.py
index cafbde0..e260465 100644
--- a/python/libs/iEEG.py
+++ b/python/libs/iEEG.py
@@ -4,6 +4,88 @@
from mne_bids import write_raw_bids, BIDSPath
import json
+class ReadError(PermissionError):
+ """Raised when a PermissionError is thrown while reading a file"""
+ pass
+
+class WriteError(PermissionError):
+ """Raised when a PermissionError is thrown while writing a file"""
+ pass
+
+metadata = {
+ 'eeg': {
+ 'Task Name': 'TaskName',
+ 'Institution Name': 'InstitutionName',
+ 'Institution Address': 'InstitutionAddress',
+ 'Manufacturer': 'Manufacturer',
+ 'Manufacturer\'s Model Name': 'ManufacturersModelName',
+ 'Software Versions': 'SoftwareVersions',
+ 'Task Description': 'TaskDescription',
+ 'Instructions': 'Instructions',
+ 'CogAtlas ID': 'CogAtlasID',
+ 'CogPOID': 'CogPOID',
+ 'Device Serial Number': 'DeviceSerialNumber',
+ 'Reference': 'EEGReference',
+ 'Sampling Frequency': 'SamplingFrequency',
+ 'Power Line Frequency': 'PowerLineFrequency',
+ 'Software Filters': 'SoftwareFilters',
+ 'Cap Manufacturer': 'CapManufacturer',
+ 'Cap Manufacturer\'s Model Name': 'CapManufacturersModelName',
+ 'EEG Channel Count': 'EEGChannelCount',
+ 'EOG Channel Count': 'EOGChannelCount',
+ 'ECG Channel Count': 'ECGChannelCount',
+ 'EMG Channel Count': 'EMGChannelCount',
+ 'Misc Channel Count': 'MiscChannelCount',
+ 'Trigger Channel Count': 'TriggerChannelCount',
+ 'Recording Duration': 'RecordingDuration',
+ 'RecordingType': 'RecordingType',
+ 'Epoch Length': 'EpochLength',
+ 'Ground': 'EEGGround',
+ 'Head Circumference': 'HeadCircumference',
+ 'Placement Scheme': 'EEGPlacementScheme',
+ 'Hardware Filters': 'HardwareFilters',
+ 'Subject Artefact Description': 'SubjectArtefactDescription',
+ },
+ 'ieeg': {
+ 'Task Name': 'TaskName',
+ 'Institution Name': 'InstitutionName',
+ 'Institution Address': 'InstitutionAddress',
+ 'Manufacturer': 'Manufacturer',
+ 'Manufacturer\'s Model Name': 'ManufacturersModelName',
+ 'Software Versions': 'SoftwareVersions',
+ 'Task Description': 'TaskDescription',
+ 'Instructions': 'Instructions',
+ 'CogAtlas ID': 'CogAtlasID',
+ 'CogPOID': 'CogPOID',
+ 'Device Serial Number': 'DeviceSerialNumber',
+ 'Reference': 'iEEGReference',
+ 'Sampling Frequency': 'SamplingFrequency',
+ 'Power Line Frequency': 'PowerLineFrequency',
+ 'Software Filters': 'SoftwareFilters',
+ 'DC Offset Correction': 'DCOffsetCorrection',
+ 'Hardware Filters': 'HardwareFilters',
+ 'Electrode Manufacturer': 'ElectrodeManufacturer',
+ 'Electrode Manufacturer\'s Model Name': 'ElectrodeManufacturersModelName',
+ 'ECOG Channel Count': 'ECOGChannelCount',
+ 'SEEG Channel Count': 'SEEGChannelCount',
+ 'EEG Channel Count': 'EEGChannelCount',
+ 'EOG Channel Count': 'EOGChannelCount',
+ 'ECG Channel Count': 'ECGChannelCount',
+ 'EMG Channel Count': 'EMGChannelCount',
+ 'Misc Channel Count': 'MiscChannelCount',
+ 'Trigger Channel Count': 'TriggerChannelCount',
+ 'Recording Duration': 'RecordingDuration',
+ 'RecordingType': 'RecordingType',
+ 'Epoch Length': 'EpochLength',
+ 'Ground': 'iEEGGround',
+ 'Placement Scheme': 'iEEGPlacementScheme',
+ 'iEEG Electrode Groups': 'iEEGElectrodeGroups',
+ 'Subject Artefact Description': 'SubjectArtefactDescription',
+ 'Electrical Stimulation': 'ElectricalStimulation',
+ 'Electrical Stimulation Parameters': 'ElectricalStimulationParameters',
+ }
+}
+
# TarFile - tarfile the BIDS data.
class TarFile:
# data = { bids_directory: '../path/to/bids/output', output_time: 'bids output time' }
@@ -31,15 +113,17 @@ class Anonymize:
header = []
# data = { file_path: 'path to iEEG file' }
- def __init__(self, data):
- self.file_path = data['file_path']
+ def __init__(self, file_path):
+ self.file_path = file_path
- # read EDF file from file_path,
- file_in = EDF.EDFReader(fname=self.file_path)
-
- # read header of EDF file.
- self.header = file_in.readHeader()
- file_in.close()
+ try:
+ # read EDF file from file_path,
+ file_in = EDF.EDFReader(fname=self.file_path)
+ # read header of EDF file.
+ self.header = file_in.readHeader()
+ file_in.close()
+ except PermissionError as ex:
+ raise ReadError(ex)
def get_header(self):
return self.header
@@ -74,15 +158,15 @@ def __init__(self, data):
if data['modality'] == 'eeg':
modality = 'eeg'
- for i, file_path in enumerate(data['file_paths']):
+ for i, file in enumerate(data['edfData']['files']):
self.to_bids(
- file=file_path,
+ file=file['path'],
ch_type=modality,
task=data['taskName'],
bids_directory=data['bids_directory'],
subject_id=data['participantID'],
session=data['session'],
- split=((i+1) if len(data['file_paths']) > 1 else None),
+ split=((i+1) if len(data['edfData']['files']) > 1 else None),
output_time=data['output_time'],
read_only=data['read_only'],
line_freq=data['line_freq']
@@ -112,7 +196,11 @@ def to_bids(self,
read_only=False,
line_freq='n/a'):
if self.validate(file):
- reader = EDF.EDFReader(fname=file)
+ try:
+ reader = EDF.EDFReader(fname=file)
+ except PermissionError as ex:
+ raise ReadError(ex)
+
m_info, c_info = reader.open(fname=file)
self.set_m_info(m_info)
@@ -121,19 +209,28 @@ def to_bids(self,
if read_only:
return True
- raw.set_channel_types({ch: ch_type for ch in raw.ch_names})
+ ch_types = {}
+ for ch in raw.ch_names:
+ ch_name = ch.lower()
+ if 'eeg' in ch_name:
+ ch_types[ch] = 'eeg'
+ elif 'eog' in ch_name:
+ ch_types[ch] = 'eog'
+ elif 'ecg' in ch_name or 'ekg' in ch_name:
+ ch_types[ch] = 'ecg'
+ elif 'lflex' in ch or 'rflex'in ch or 'chin' in ch:
+ ch_types[ch] = 'emg'
+ elif 'trigger' in ch:
+ ch_types[ch] = 'stim'
+
+ else:
+ ch_types[ch] = ch_type
+
+ raw.set_channel_types(ch_types)
- os.makedirs(bids_directory + os.path.sep + output_time, exist_ok=True)
- bids_directory = bids_directory + os.path.sep + output_time
- bids_root = bids_directory
-
m_info['subject_id'] = subject_id
subject = m_info['subject_id'].replace('_', '').replace('-', '').replace(' ', '')
- bids_basename = BIDSPath(subject=subject, task=task, root=bids_root, acquisition=ch_type, split=split)
- session = session
- bids_basename.update(session=session)
-
raw.info['line_freq'] = line_freq
raw._init_kwargs = {
@@ -145,12 +242,23 @@ def to_bids(self,
'preload': False,
'verbose': None
}
+
try:
+ os.makedirs(bids_directory + os.path.sep + output_time, exist_ok=True)
+ bids_directory = bids_directory + os.path.sep + output_time
+ bids_root = bids_directory
+
+ bids_basename = BIDSPath(subject=subject, task=task, root=bids_root, acquisition=ch_type, split=split)
+ bids_basename.update(session=session)
+
write_raw_bids(raw, bids_basename, overwrite=False, verbose=False)
with open(bids_basename, 'r+b') as f:
f.seek(8) # id_info field starts 8 bytes in
f.write(bytes("X X X X".ljust(80), 'ascii'))
+ except PermissionError as ex:
+ raise WriteError(ex)
+
except Exception as ex:
print(ex)
print('finished')
diff --git a/python/libs/loris_api.py b/python/libs/loris_api.py
index f4b9180..c855b87 100644
--- a/python/libs/loris_api.py
+++ b/python/libs/loris_api.py
@@ -4,29 +4,30 @@
class LorisAPI:
- url = 'https://localhost/api/v0.0.4-dev/'
+ url = ''
username = ''
password = ''
-
token = ''
-
- def __init__(self):
- # self.login()
-
+
def login(self):
- resp = json.loads(requests.post(
+ resp = requests.post(
url = self.url + 'login',
json = {
- 'username': self.username,
+ 'username': self.username,
'password': self.password
},
verify = False
- ).content.decode('ascii'))
+ )
+
+ print(resp)
+
+ resp_json = json.loads(resp.content.decode('ascii'))
- if resp.get('error'):
- raise RuntimeError(resp.get('error'))
+ if resp_json.get('error'):
+ raise RuntimeError(resp_json.get('error'))
+
+ self.token = resp_json.get('token')
- self.token = resp.get('token')
print(self.token)
def get_projects(self):
@@ -39,13 +40,31 @@ def get_projects(self):
json_resp = json.loads(resp.content.decode('ascii'))
return json_resp.get('Projects')
+ def get_all_subprojects(self):
+ resp = requests.get(
+ url=self.url + 'subprojects',
+ headers={'Authorization': 'Bearer %s' % self.token, 'LORIS-Overwrite': 'overwrite'},
+ verify=False
+ )
+
+ print (resp)
+ json_resp = json.loads(resp.content.decode('ascii'))
+ return json_resp.get('Subprojects')
+
def get_subprojects(self, project):
project = self.get_project(project)
return project.get('Subprojects')
- def get_visits(self, project):
- project = self.get_project(project)
- return project.get('Visits')
+ def get_visits(self, subproject):
+ resp = requests.get(
+ url=self.url + 'subprojects/' + urllib.parse.quote(subproject),
+ headers={'Authorization': 'Bearer %s' % self.token, 'LORIS-Overwrite': 'overwrite'},
+ verify=False
+ )
+
+ print (resp)
+ json_resp = json.loads(resp.content.decode('ascii'))
+ return json_resp.get('Visits')
def get_sites(self):
resp = requests.get(
@@ -129,16 +148,16 @@ def start_next_stage(self, candid, visit, site, subproject, project, date):
print(resp.status_code)
print(resp.text)
- def create_candidate(self):
+ def create_candidate(self, project, dob, sex, site):
resp = requests.post(
- url=self.url + '/candidates/',
- headers={'Authorization': 'Bearer %s' % self.token, 'LORIS-Overwrite': 'overwrite'},
- data=json.dumps({
- "Candidate": {
- "Project": 'Pumpernickel',
- "DoB": "1985-12-22",
- "Sex": "Female",
- "Site": 'Montreal',
+ url = self.url + '/candidates/',
+ headers = {'Authorization': 'Bearer %s' % self.token, 'LORIS-Overwrite': 'overwrite'},
+ data = json.dumps({
+ "Candidate" : {
+ "Project" : project,
+ "DoB" : dob,
+ "Sex" : sex,
+ "Site" : site,
}
}),
verify=False
@@ -147,3 +166,22 @@ def create_candidate(self):
print(resp)
json_resp = json.loads(resp.content.decode('ascii'))
print(json_resp)
+ return json_resp
+
+ def create_visit(self, candid, visit, site, project, subproject):
+ resp = requests.put(
+ url = self.url + '/candidates/' + candid + '/' + visit,
+ headers = {'Authorization': 'Bearer %s' % self.token, 'LORIS-Overwrite': 'overwrite'},
+ data = json.dumps({
+ "CandID" : candid,
+ "Visit" : visit,
+ "Site" : site,
+ "Battery": subproject,
+ "Project" : project
+ }),
+ verify = False
+ )
+
+ print(resp)
+ #json_resp = json.loads(resp.content.decode('ascii'))
+ #print(json_resp)
diff --git a/src/Settings.js b/src/Settings.js
index 02e3e3e..69ac7e6 100644
--- a/src/Settings.js
+++ b/src/Settings.js
@@ -11,8 +11,6 @@ const Settings = () => {
const [background, setBackground] = useState('#25c4b1');
const handleChangeComplete = (color) => {
- console.info(color);
- console.info(background);
setBackground(color.hex);
};
/**
diff --git a/src/css/App.css b/src/css/App.css
index b86ece2..109947b 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -3,8 +3,6 @@ html {
-webkit-app-region: drag;
color: rgb(37, 35, 38);
/*color: rgb(255, 255, 255);*/
- background-color: #064785;
- /*background-color: rgb(10, 130, 110);*/
}
body {
diff --git a/src/css/index.css b/src/css/index.css
index 1291204..14b1a0f 100644
--- a/src/css/index.css
+++ b/src/css/index.css
@@ -2,9 +2,6 @@ html {
height: 100%;
-webkit-app-region: drag;
color: rgb(37, 35, 38);
- /*color: rgb(255, 255, 255);*/
- background-color: #064785;
- /*background-color: rgb(10, 130, 110);*/
}
body {
@@ -15,7 +12,10 @@ body {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue',
'Segoe UI', 'Roboto', 'Ubuntu', 'Cantarell', sans-serif;
}
-
+.errors {
+ background: #fff;
+ padding: 0.1px 15px;
+}
.header {
color: #f6ffff;
padding: 20px;
@@ -31,7 +31,6 @@ body {
.info {
padding: 14px;
background-color: #f3f4f8;
- /*background-color: #039b83;*/
}
.half {
width: 50%;
@@ -77,7 +76,7 @@ input {
.alert {
position: relative;
padding: .75rem 1.25rem;
- margin: 1rem 0 2rem 0;
+ margin: 1rem 0 1rem 0;
border: 1px solid transparent;
border-radius: .25rem;
}
diff --git a/src/jsx/Configuration.js b/src/jsx/Configuration.js
index a4b3a5f..7b021c6 100644
--- a/src/jsx/Configuration.js
+++ b/src/jsx/Configuration.js
@@ -36,10 +36,14 @@ const Configuration = (props) => {
// React State
const state = {};
+ state.errors = {};
+ [state.errors.get, state.errors.set] = useState([]);
state.edfFiles = {};
[state.edfFiles.get, state.edfFiles.set] = useState([]);
- state.edfType = {};
- [state.edfType.get, state.edfType.set] = useState('ieeg');
+ state.edfData = {};
+ [state.edfData.get, state.edfData.set] = useState([]);
+ state.modality = {};
+ [state.modality.get, state.modality.set] = useState('ieeg');
state.eventsTSV = {};
[state.eventsTSV.get, state.eventsTSV.set] = useState([]);
state.annotationsTSV = {};
@@ -64,8 +68,6 @@ const Configuration = (props) => {
[state.projectUseAPI.get, state.projectUseAPI.set] = useState(false);
state.subprojectID = {};
[state.subprojectID.get, state.subprojectID.set] = useState('n/a');
- state.recordingDate = {};
- [state.recordingDate.get, state.recordingDate.set] = useState(null);
state.subprojectOptions = {};
[state.subprojectOptions.get, state.subprojectOptions.set] = useState([]);
state.subprojectUseAPI = {};
@@ -73,10 +75,10 @@ const Configuration = (props) => {
state.session = {};
[state.session.get, state.session.set] = useState('');
state.sessionOptions = {};
- state.eegMetadataFile = {};
- [state.eegMetadataFile.get, state.eegMetadataFile.set] = useState([]);
- state.eegMetadata = {};
- [state.eegMetadata.get, state.eegMetadata.set] = useState([]);
+ state.bidsMetadataFile = {};
+ [state.bidsMetadataFile.get, state.bidsMetadataFile.set] = useState([]);
+ state.bidsMetadata = {};
+ [state.bidsMetadata.get, state.bidsMetadata.set] = useState(null);
state.lineFreq = {};
[state.lineFreq.get, state.lineFreq.set] = useState('');
state.taskName = {};
@@ -94,7 +96,7 @@ const Configuration = (props) => {
[
state.participantEntryMode.get,
state.participantEntryMode.set,
- ] = useState('loris');
+ ] = useState('manual');
state.participantID = {};
[state.participantID.get, state.participantID.set] = useState('');
state.participantDOB = {};
@@ -109,50 +111,56 @@ const Configuration = (props) => {
[state.anonymize.get, state.anonymize.set] = useState(false);
state.subjectID = {};
[state.subjectID.get, state.subjectID.set] = useState('');
- state.edfHeader = {};
- [state.edfHeader.get, state.edfHeader.set] = useState({
- subject_id: '', recording_id: '',
- day: '', month: '', year: '',
- hour: '', minute: '', second: '',
- subtype: '',
- });
- const [authCredentialsVisible, setAuthCredentialsVisible] = useState(false);
+ state.authCredentialsVisible = {};
+ [
+ state.authCredentialsVisible.get,
+ state.authCredentialsVisible.set,
+ ] = useState(false);
+ state.isAuthenticated = {};
+ [state.isAuthenticated.get, state.isAuthenticated.set] = useState(false);
+ /**
+ * Similar to componentDidMount and componentDidUpdate.
+ */
useEffect(() => {
Object.keys(state).map((key) => appContext.setTask(key, state[key].get));
}, []);
useEffect(() => {
- Object.keys(state).map((key) => appContext.setTask(key, state[key].get));
- }, []);
+ state.errors.set([]);
+ }, [state.edfFiles.get]);
+
+ useEffect(() => {
+ console.log(state.isAuthenticated.get);
+ }, [state.isAuthenticated.get]);
- /**
- * Similar to componentDidMount and componentDidUpdate.
- */
useEffect(() => {
- Object.keys(state.edfHeader.get).map((key) => {
- appContext.setTask(key, state.edfHeader.get[key]);
- });
+ console.log(state.participantEntryMode.get);
+ }, [state.participantEntryMode.get]);
- if (!isNaN(parseInt(state.edfHeader.get['year']))) {
- const date = new Date(
- state.edfHeader.get['year'] < 85 ?
- Number('20' + state.edfHeader.get['year']) :
- Number('19' + state.edfHeader.get['year']),
- Number(state.edfHeader.get['month']-1),
- Number(state.edfHeader.get['day']),
- Number(state.edfHeader.get['hour']),
- Number(state.edfHeader.get['minute']),
- Number(state.edfHeader.get['second']),
- );
- state.recordingDate.set(date);
- appContext.setTask('recordingDate', date);
+ useEffect(() => {
+ if (socketContext) {
+ socketContext.emit('get_edf_data', {
+ files: state.edfFiles.get.map((edfFile) =>
+ ({
+ path: edfFile['path'],
+ name: edfFile['name'],
+ })),
+ });
}
- }, [state.edfHeader.get]);
+ }, [state.edfFiles.get]);
+
+ useEffect(() => {
+ if (socketContext) {
+ if (state.bidsMetadataFile.get.length > 0) {
+ socketContext.emit('get_bids_metadata', {
+ file_path: state.bidsMetadataFile.get[0]['path'],
+ modality: state.modality.get,
+ });
+ }
+ }
+ }, [state.bidsMetadataFile.get, state.modality.get]);
- /**
- * Similar to componentDidMount and componentDidUpdate.
- */
useEffect(() => {
if (socketContext) {
// socketContext.emit('get_loris_sites');
@@ -189,24 +197,42 @@ const Configuration = (props) => {
state.sessionOptions.set(visitOpts);
});
- socketContext.on('edf_header', (message) => {
+ socketContext.on('edf_data', (message) => {
if (message['error']) {
console.error(message['error']);
+ state.errors.set([...state.errors.get, message['error']]);
}
- if (message['header']) {
- state.edfHeader.set(message['header']);
- state.subjectID.set(message['header']['subject_id']);
+ if (message['date']) {
+ message['date'] = new Date(message['date']);
}
+
+ state.subjectID.set(message?.['subjectID'] || '');
+ state.edfData.set(message);
+ appContext.setTask('edfData', message);
});
- socketContext.on('metadata', (message) => {
+ socketContext.on('bids_metadata', (message) => {
if (message['error']) {
console.error(message['error']);
}
- if (message['metadata']) {
- state.eegMetadata.set(message['metadata']);
+ state.bidsMetadata.set(message);
+ appContext.setTask('bidsMetadata', message);
+ });
+
+ socketContext.on('new_candidate_created', (data) => {
+ console.log(data);
+ state.participantID.set(data['PSCID']);
+ appContext.setTask('participantID', data['PSCID']);
+ });
+
+ socketContext.on('loris_login_response', (data) => {
+ if (data.error) {
+ // todo display error message - login failure
+ } else {
+ state.isAuthenticated.set(true);
+ state.participantEntryMode.set('new_loris');
}
});
}
@@ -221,18 +247,27 @@ const Configuration = (props) => {
const onUserInput = (name, value) => {
// Update the state of Configuration.
switch (name) {
- case 'edfFiles':
- state.edfFiles.set(value);
- createHeaderFields(value[0]['path']);
+ case 'recordingID':
+ state.edfData.set((prevState) => {
+ return {...prevState, [name]: value};
+ });
+ appContext.setTask(name, value);
break;
- case 'eegMetadataFile':
- state.eegMetadataFile.set(value);
- createMetadataFields(value[0]['path']);
+ case 'subjectID':
+ state.edfData.set((prevState) => {
+ return {...prevState, [name]: value};
+ });
+ state.subjectID.set(value);
+ appContext.setTask(name, value);
break;
case 'LORIScompliant':
if (value === 'yes') {
value = true;
- state.participantEntryMode.set('loris');
+ if (state.isAuthenticated.get) {
+ state.participantEntryMode.set('new_loris');
+ } else {
+ state.participantEntryMode.set('manual');
+ }
} else {
value = false;
state.participantEntryMode.set('manual');
@@ -240,7 +275,7 @@ const Configuration = (props) => {
state.LORIScompliant.set(value);
break;
case 'siteID_API':
- if (value === 'Manual entry') {
+ if (value == 'Enter manually') {
value = '';
state.siteUseAPI.set(false);
} else {
@@ -254,13 +289,12 @@ const Configuration = (props) => {
name = 'siteID';
break;
case 'projectID_API':
- if (value === 'Manual entry') {
+ if (value == 'Enter manually') {
state.projectUseAPI.set(false);
value = '';
} else {
state.projectUseAPI.set(true);
socketContext.emit('get_loris_subprojects', value);
- socketContext.emit('get_loris_visits', value);
}
state.projectID.set(value);
name = 'projectID';
@@ -270,11 +304,12 @@ const Configuration = (props) => {
name = 'projectID';
break;
case 'subprojectID_API':
- if (value === 'Manual entry') {
+ if (value == 'Enter manually') {
state.subprojectUseAPI.set(false);
value = '';
} else {
state.subprojectUseAPI.set(true);
+ socketContext.emit('get_loris_visits', value);
}
state.subprojectID.set(value);
name = 'subprojectID';
@@ -284,7 +319,7 @@ const Configuration = (props) => {
name = 'subprojectID';
break;
case 'session_API':
- if (value === 'Manual entry') {
+ if (value == 'Enter manually') {
state.sessionUseAPI.set(false);
value = '';
} else {
@@ -299,9 +334,15 @@ const Configuration = (props) => {
break;
case 'anonymize':
if (value) {
- anonymizeHeaderValues();
+ state.edfData.set((prevState) => {
+ return {...prevState, ['subjectID']: 'X X X X'};
+ });
+ appContext.setTask('subjectID', 'X X X X');
} else {
- onUserHeaderFieldInput('subject_id', state.subjectID.get);
+ state.edfData.set((prevState) => {
+ return {...prevState, ['subjectID']: state.subjectID.get};
+ });
+ appContext.setTask('subjectID', state.subjectID.get);
}
state.anonymize.set(value);
break;
@@ -316,40 +357,6 @@ const Configuration = (props) => {
}
};
- /**
- * onUserHeaderFieldInput - input change by user.
- * @param {string} name - element name
- * @param {object|string} value - element value
- */
- const onUserHeaderFieldInput = (name, value) => {
- state.edfHeader.set((prevState) => {
- return {...prevState, [name]: value};
- });
- // Update the 'task' of app context.
- appContext.setTask(name, value);
- };
-
- /**
- * createHeaderFields - EDF file given from user.
- * @param {string} path - edf file path
- * Creates Header fields for EDF file.
- */
- const createHeaderFields = (path) => {
- socketContext.emit('ieeg_get_header', {
- file_path: path,
- });
- };
-
- /**
- * createMetadataFields - Metadata file given from user.
- * @param {string} path - metadata file path
- */
- const createMetadataFields = (path) => {
- socketContext.emit('get_metadata', {
- file_path: path,
- });
- };
-
/**
* arrayToObject - Convert an array to an object
* { value: value }
@@ -368,21 +375,26 @@ const Configuration = (props) => {
};
/**
- * anonymizeHeaderValues - anonymize iEEG header values.
- */
- const anonymizeHeaderValues = () => {
- const keys = ['subject_id'];
- const anonymize = {subject_id: 'X X X X'};
- for (const key of keys) {
- onUserHeaderFieldInput(key, anonymize[key]);
- appContext.setTask(key, anonymize[key]);
- }
- };
-
- /**
- * createCandidate
+ * create candidate
*/
const createCandidate = () => {
+ if (socketContext) {
+ const dob = state.participantDOB.get.toISOString().replace(/T.*/, '');
+ if (!state.edfData.get?.['date']) return;
+
+ const visitDate = state.edfData.get['date']
+ .toISOString().replace(/T.*/, '');
+
+ socketContext.emit('create_candidate_and_visit', {
+ project: state.projectID.get,
+ dob: dob,
+ sex: state.participantSex.get,
+ site: state.siteID.get,
+ subproject: state.subprojectID.get,
+ visit: state.session.get,
+ date: visitDate,
+ });
+ }
};
/**
@@ -390,16 +402,23 @@ const Configuration = (props) => {
* @param {boolean} hidden
*/
const hideAuthCredentials = (hidden) => {
- setAuthCredentialsVisible(!hidden);
+ state.authCredentialsVisible.set(!hidden);
};
return props.visible ? (
<>
+ ⚠ + Invalid keys for the selected modality will be ignored. +
, + ); + } else if ('error' in appContext.getFromTask('bidsMetadata')) { + metadataReport.push( +