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 ? ( <> +
+ {state.errors.get.map((error, index) => +
+ ❌ {error} +
, + )} +
- Data Configuration + Select data and metadata
@@ -414,11 +433,15 @@ const Configuration = (props) => { required={true} onUserInput={onUserInput} /> -
Can be split into multiple EDF files
+
+ + Multiple EDF files can be selected for a single recording + +
- { ieeg: 'Stereo iEEG', eeg: 'EEG', }} - checked={state.edfType.get} + checked={state.modality.get} />
@@ -457,12 +480,12 @@ const Configuration = (props) => { />
- eegMetadataFile['name'], + state.bidsMetadataFile.get.map( + (bidsMetadataFile) => bidsMetadataFile['name'], ).join(', ') } label='Parameter metadata (tsv)' @@ -473,7 +496,7 @@ const Configuration = (props) => { @@ -520,7 +543,7 @@ const Configuration = (props) => { label='' required={true} value={state.siteID.get} - emptyOption='Manual entry' + emptyOption='Enter manually' options={arrayToObject(state.siteOptions.get)} onUserInput={onUserInput} /> @@ -547,7 +570,7 @@ const Configuration = (props) => { label='' required={true} value={state.projectID.get} - emptyOption='Manual entry' + emptyOption='Enter manually' options={arrayToObject(state.projectOptions.get)} onUserInput={onUserInput} /> @@ -574,7 +597,7 @@ const Configuration = (props) => { label='' required={true} value={state.subprojectID.get} - emptyOption='Manual entry' + emptyOption='Enter manually' options={arrayToObject(state.subprojectOptions.get)} onUserInput={onUserInput} /> @@ -594,10 +617,10 @@ const Configuration = (props) => {
@@ -607,7 +630,7 @@ const Configuration = (props) => { label='' required={true} value={state.session.get} - emptyOption='Manual entry' + emptyOption='Enter manually' options={arrayToObject(state.sessionOptions.get)} onUserInput={onUserInput} /> @@ -669,47 +692,56 @@ const Configuration = (props) => {
- Participant Data + Participant Details
- {state.LORIScompliant.get && + {state.LORIScompliant.get && state.isAuthenticated.get &&
} - {state.participantEntryMode.get == 'loris' && + {state.participantEntryMode.get == 'new_loris' && + state.isAuthenticated.get && <>
onUserInput('participantDOB', date)} />
@@ -717,7 +749,7 @@ const Configuration = (props) => {
{ /> } + {state.participantEntryMode.get == 'existing_loris' && + state.isAuthenticated.get && + <> +
+ +
+
+ +
+ + } {state.participantEntryMode.get == 'manual' && <>
@@ -762,7 +821,7 @@ const Configuration = (props) => {
{
{ name='anonymize' onChange={(checked) => onUserInput('anonymize', checked)} checked={state.anonymize.get} + disabled={state.edfData.get?.files?.length > 0 ? false : true} /> Anonymize @@ -818,15 +878,12 @@ const Configuration = (props) => {
- { - state.subjectID.set(value); - onUserHeaderFieldInput(name, value); - }} - placeholder={state.edfHeader.get['subject_id']} + value={state.edfData?.get['subjectID'] || ''} + onUserInput={onUserInput} + readonly={state.edfData.get?.files?.length > 0 ? false : true} />
Recommended EDF anonymization: "X X X X"
@@ -835,12 +892,12 @@ const Configuration = (props) => {
- 0 ? false : true} />
(EDF spec: Startdate dd-MMM-yyyy @@ -853,7 +910,7 @@ const Configuration = (props) => { name='recording_date' label='Recording Date' readonly={true} - value={state.recordingDate.get ? + value={state.edfData.get?.['date'] ? new Intl.DateTimeFormat( 'en-US', { @@ -863,7 +920,7 @@ const Configuration = (props) => { hour: 'numeric', minute: 'numeric', }, - ).format(state.recordingDate.get) : + ).format(state.edfData.get['date']) : '' } /> @@ -872,7 +929,7 @@ const Configuration = (props) => {
diff --git a/src/jsx/Converter.js b/src/jsx/Converter.js index 0e277d8..f4ac723 100644 --- a/src/jsx/Converter.js +++ b/src/jsx/Converter.js @@ -48,6 +48,14 @@ const Converter = (props) => { }, }); + /** + * Similar to componentDidMount and componentDidUpdate. + */ + useEffect(() => { + // if (socketContext) {} + }, [socketContext]); + + /** * beginBidsCreation - create BIDS format. * Sent by socket to python: edf_to_bids. @@ -57,35 +65,37 @@ const Converter = (props) => { return {...prevState, ['mode']: 'loading'}; }); setModalVisible(true); - socketContext.emit('edf_to_bids', { - file_paths: appContext.getFromTask('edfFiles') ? - appContext.getFromTask('edfFiles') - .map((edfFile) => edfFile['path']) : [], - modality: appContext.getFromTask('edfType') ?? 'ieeg', - bids_directory: appContext.getFromTask('bidsDirectory') ?? '', - read_only: false, - events_tsv: appContext.getFromTask('eventsTSV').length > 0 ? - appContext.getFromTask('eventsTSV')[0]['path'] : '', - annotations_tsv: appContext.getFromTask('annotationsTSV').length > 0 ? - appContext.getFromTask('annotationsTSV')[0]['path'] : '', - annotations_json: appContext.getFromTask('annotationsJSON').length > 0 ? - appContext.getFromTask('annotationsJSON')[0]['path'] : '', - site_id: appContext.getFromTask('siteID') ?? '', - project_id: appContext.getFromTask('projectID') ?? '', - sub_project_id: appContext.getFromTask('subprojectID') ?? '', - session: appContext.getFromTask('session') ?? '', - participantID: appContext.getFromTask('participantID') ?? '', - age: appContext.getFromTask('participantAge') ?? '', - hand: appContext.getFromTask('participantHand') ?? '', - sex: appContext.getFromTask('participantSex') ?? '', - preparedBy: appContext.getFromTask('preparedBy') ?? '', - line_freq: appContext.getFromTask('lineFreq') || 'n/a', - software_filters: appContext.getFromTask('softwareFilters') ?? 'n/a', - recording_type: appContext.getFromTask('recordingType') ?? 'n/a', - taskName: appContext.getFromTask('taskName') ?? '', - reference: appContext.getFromTask('reference') ?? '', - subject_id: appContext.getFromTask('subject_id') ?? '', - }); + + if (appContext.getFromTask('edfData')?.['files'].length > 0) { + socketContext.emit('edf_to_bids', { + edfData: appContext.getFromTask('edfData') ?? [], + modality: appContext.getFromTask('modality') ?? 'ieeg', + bids_directory: appContext.getFromTask('bidsDirectory') ?? '', + read_only: false, + events_tsv: appContext.getFromTask('eventsTSV').length > 0 ? + appContext.getFromTask('eventsTSV')[0]['path'] : '', + annotations_tsv: appContext.getFromTask('annotationsTSV').length > 0 ? + appContext.getFromTask('annotationsTSV')[0]['path'] : '', + annotations_json: appContext.getFromTask('annotationsJSON').length > 0 ? + appContext.getFromTask('annotationsJSON')[0]['path'] : '', + bidsMetadata: appContext.getFromTask('bidsMetadata') ?? '', + site_id: appContext.getFromTask('siteID') ?? '', + project_id: appContext.getFromTask('projectID') ?? '', + sub_project_id: appContext.getFromTask('subprojectID') ?? '', + session: appContext.getFromTask('session') ?? '', + participantID: appContext.getFromTask('participantID') ?? '', + age: appContext.getFromTask('participantAge') ?? '', + hand: appContext.getFromTask('participantHand') ?? '', + sex: appContext.getFromTask('participantSex') ?? '', + preparedBy: preparedBy ?? '', + line_freq: appContext.getFromTask('lineFreq') || 'n/a', + software_filters: appContext.getFromTask('softwareFilters') ?? 'n/a', + recording_type: appContext.getFromTask('recordingType') ?? 'n/a', + taskName: appContext.getFromTask('taskName') ?? '', + reference: appContext.getFromTask('reference') ?? '', + subject_id: appContext.getFromTask('subject_id') ?? '', + }); + } }; /** @@ -111,32 +121,71 @@ const Converter = (props) => { setModalVisible(!hidden); }; - /** - * onMessage - received message from python. - * @param {object} message - response - */ - const onMessage = (message) => { - console.info(message); - if (message['output_time']) { - setOutputTime(message['output_time']); - appContext.setTask('output_time', message['output_time']); - setModalText((prevState) => { - return {...prevState, ['mode']: 'success'}; - }); - } else { - setModalText((prevState) => { - prevState.message['error'] = ( -
- {message['error'].map((error, i) => - {error}
)} -
- ); - return {...prevState, ['mode']: 'error'}; + useEffect(() => { + if (socketContext) { + socketContext.on('bids', (message) => { + if (message['output_time']) { + setOutputTime(message['output_time']); + appContext.setTask('output_time', message['output_time']); + setModalText((prevState) => { + return {...prevState, ['mode']: 'success'}; + }); + } else if (message['error']) { + setModalText((prevState) => { + prevState.message['error'] = ( +
+ {message['error'].map((error, i) => + {error}
)} +
+ ); + return {...prevState, ['mode']: 'error'}; + }); + } }); } - }; + }, [socketContext]); let error = false; + const metadataReport = []; + if (appContext.getFromTask('bidsMetadata')) { + if ( + 'metadata' in appContext.getFromTask('bidsMetadata') && + 'invalid_keys' in appContext.getFromTask('bidsMetadata') + ) { + const metadata = appContext.getFromTask('bidsMetadata')['metadata']; + const invalidKeys = + appContext.getFromTask('bidsMetadata')['invalid_keys']; + + Object.keys(metadata).map((key) => { + metadataReport.push( +
+ {invalidKeys.indexOf(key) > -1 ? + <> + + {key}: {metadata[key]} + : + <> + {key}: {metadata[key]} + + } +
, + ); + }); + metadataReport.push( +

+ + Invalid keys for the selected modality will be ignored. +

, + ); + } else if ('error' in appContext.getFromTask('bidsMetadata')) { + metadataReport.push( +
+ + {appContext.getFromTask('bidsMetadata')['error']} +
, + ); + } + } /** * Renders the React component. @@ -155,19 +204,25 @@ const Converter = (props) => {
Review your data configuration:
- {(appContext.getFromTask('edfFiles') && - appContext.getFromTask('edfFiles').length > 0) ? - <> - - EDF data file(s):  - {appContext.getFromTask('edfFiles') - .map((edfFile) => edfFile['name']).join(', ') - } - : - <> + {appContext.getFromTask('edfData')?.['error'] ? +
{error = true} - No EDF file selected. - + + {appContext.getFromTask('edfData')['error']} +
: + appContext.getFromTask('edfData') && + appContext.getFromTask('edfData')?.['files']?.length > 0 ? + <> + + EDF data file(s):  + {appContext.getFromTask('edfData')['files'] + .map((edfFile) => edfFile['name']).join(', ') + } + : + <> + {error = true} + No EDF file selected. + }
@@ -278,12 +333,12 @@ const Converter = (props) => { {appContext.getFromTask('session') ? <> - Session label: {appContext.getFromTask('session')} + Session: {appContext.getFromTask('session')} : <> {error = true} - Session label is not specified. + Session is not specified. }
@@ -314,7 +369,7 @@ const Converter = (props) => {
- Review your participant data: + Review your participant details:
{appContext.getFromTask('participantID') ? <> @@ -331,13 +386,27 @@ const Converter = (props) => { }
+
+ Review your uploaded EEG Parameter metadata: +
+ {metadataReport.length > 0 ? + <> + {metadataReport} + : + <> + + No EEG Parameter metadata file selected. + + } +
+
Verify anonymization of EDF header data:
- {appContext.getFromTask('subject_id') ? + {appContext.getFromTask('subjectID') ? <> Subject ID:  - {appContext.getFromTask('subject_id')} + {appContext.getFromTask('subjectID')} : <> @@ -351,7 +420,7 @@ const Converter = (props) => { {appContext.getFromTask('recording_id')}
} - {appContext.getFromTask('recordingDate') && + {appContext.getFromTask('edfData')?.['date'] &&
Recording Date:  {new Intl.DateTimeFormat( @@ -363,7 +432,7 @@ const Converter = (props) => { hour: 'numeric', minute: 'numeric', }, - ).format(appContext.getFromTask('recordingDate'))} + ).format(appContext.getFromTask('edfData')['date'])}
}
@@ -413,7 +482,6 @@ const Converter = (props) => { > {modalText.message[modalText.mode]} - ) : null; }; diff --git a/src/jsx/elements/authentication.js b/src/jsx/elements/authentication.js index 218d6b1..a056d8c 100644 --- a/src/jsx/elements/authentication.js +++ b/src/jsx/elements/authentication.js @@ -30,7 +30,6 @@ export const AuthenticationMessage = (props) => { useEffect(async () => { const myAPI = window['myAPI']; const credentials = await myAPI.getLorisAuthenticationCredentials(); - console.log(credentials); if (credentials && credentials.lorisURL && credentials.lorisUsername && @@ -54,7 +53,8 @@ export const AuthenticationMessage = (props) => { if (data.error) { // todo display error message - login failure } else { - setLoginMessage(`LORIS Account set as ${appContext.lorisUsername}`); + console.log(data); + setLoginMessage(`LORIS Account set as ${data.lorisUsername}`); setLoginLink('Sign in to another account..'); } }); diff --git a/src/jsx/elements/inputs.js b/src/jsx/elements/inputs.js index ad7bf00..2753feb 100644 --- a/src/jsx/elements/inputs.js +++ b/src/jsx/elements/inputs.js @@ -492,5 +492,5 @@ TextareaInput.propTypes = { TextareaInput.defaultProps = { required: false, rows: 4, - cols: 25, + cols: 23, }; diff --git a/wiki/ubuntu/README.md b/wiki/ubuntu/README.md index a38a6e3..68f4c87 100644 --- a/wiki/ubuntu/README.md +++ b/wiki/ubuntu/README.md @@ -21,7 +21,7 @@ sudo apt-get install python3-venv python3 -m venv . source bin/activate pip install -r requirements.txt -python3 -m python.pycat +python3 -m python.eeg2bids ``` **Note:** Both the "python-service" & the "electron-app" need to be running simultaneously for EEG2BIDS Wizard to successfully function in development!