diff --git a/python/eeg2bids.py b/python/eeg2bids.py index ed4bb91..13e7c38 100644 --- a/python/eeg2bids.py +++ b/python/eeg2bids.py @@ -38,8 +38,8 @@ def connect(sid, environ): return False # extra precaution. -def tarfile_bids_thread(data): - iEEG.TarFile(data) +def tarfile_bids_thread(bids_directory): + iEEG.TarFile(bids_directory) response = { 'compression_time': 'example_5mins' } @@ -47,9 +47,8 @@ def tarfile_bids_thread(data): @sio.event -def tarfile_bids(sid, data): - # data = { bids_directory: '../path/to/bids/output', output_time: 'bids output time' } - response = eventlet.tpool.execute(tarfile_bids_thread, data) +def tarfile_bids(sid, bids_directory): + response = eventlet.tpool.execute(tarfile_bids_thread, bids_directory) send = { 'compression_time': response['compression_time'] } @@ -288,16 +287,14 @@ def edf_to_bids(sid, data): @sio.event -def validate_bids(sid, data): - # data = { bids_directory: '../path/to/bids/output', output_time: 'bids output time' } - print('validate_bids: ', data) +def validate_bids(sid, bids_directory): + print('validate_bids: ', bids_directory) error_messages = [] - if 'bids_directory' not in data or not data['bids_directory']: + if not bids_directory: error_messages.append('The BIDS output directory is missing.') - 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) + BIDS.Validate(bids_directory) response = { 'file_paths': BIDS.Validate.file_paths, 'result': BIDS.Validate.result diff --git a/python/libs/BIDS.py b/python/libs/BIDS.py index a47b21e..b9c5fe1 100644 --- a/python/libs/BIDS.py +++ b/python/libs/BIDS.py @@ -6,14 +6,12 @@ class Validate: file_paths = [] result = [] - def __init__(self, data): + def __init__(self, bids_directory): print('- Validate: init started.') - sep = os.path.sep - start_path = data['bids_directory'] + sep + data['output_time'] # current directory file_paths = [] result = [] validator = BIDSValidator() - for path, dirs, files in os.walk(start_path): + for path, dirs, files in os.walk(bids_directory): for filename in files: if filename == '.bidsignore': continue @@ -25,9 +23,9 @@ def __init__(self, data): 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)])) - # print(validator.is_bids(temp[len(start_path):len(temp)])) + file_paths.append(temp[len(bids_directory):len(temp)]) + result.append(validator.is_bids(temp[len(bids_directory):len(temp)])) + # print(validator.is_bids(temp[len(bids_directory):len(temp)])) self.set_file_paths(file_paths) self.set_result(result) diff --git a/python/libs/Modifier.py b/python/libs/Modifier.py index c4d06c3..e90bda0 100644 --- a/python/libs/Modifier.py +++ b/python/libs/Modifier.py @@ -29,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, @@ -40,29 +40,29 @@ def get_eeg_path(self): def clean_dataset_files(self): if len(self.data['edfData']['files']) > 0: - # for split recording, clean the duplicates _eeg.json and _channels.tsv + # for multiple run 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) - + 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 + os.remove(filename) + + # remove the run 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]) + re.sub(r"_run-[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]) + re.sub(r"_run-[0-9]+", '', sidecar_files[0]) ) os.rename(fileOrig, fileDest) @@ -84,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(), @@ -180,13 +180,13 @@ def copy_annotation_files(self): for eegRun in self.data.get('eegRuns'): edf_file = eegRun['edfBIDSBasename'] filename = os.path.join(self.get_eeg_path(), edf_file + '_annotations') - + if eegRun['annotationsTSV']: shutil.copyfile( eegRun['annotationsTSV'], os.path.join(self.get_eeg_path(), filename + '.tsv') ) - + if eegRun['annotationsJSON']: shutil.copyfile( eegRun['annotationsJSON'], @@ -198,7 +198,7 @@ def copy_event_files(self): for eegRun in self.data.get('eegRuns'): edf_file = eegRun['edfBIDSBasename'] filename = os.path.join(self.get_eeg_path(), edf_file + '_annotations') - + if eegRun['eventFile']: # events.tsv data collected: output = [] @@ -228,7 +228,7 @@ def copy_event_files(self): temp = os.path.join(path, filename) if temp.endswith(eegRun['edfBIDSBasename'] + '_events.tsv'): path_event_files = temp - + # We now open BIDS events.tsv file if it exists if path_event_files: try: @@ -272,7 +272,7 @@ 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) @@ -284,12 +284,12 @@ def modify_eeg_json(self): referenceField = 'EGGReference' 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']: + if key not in self.data['bidsMetadata']['invalid_keys']: file_data[key] = 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 78f0cd1..12c473b 100644 --- a/python/libs/iEEG.py +++ b/python/libs/iEEG.py @@ -91,23 +91,20 @@ class WriteError(PermissionError): # TarFile - tarfile the BIDS data. class TarFile: - # data = { bids_directory: '../path/to/bids/output', output_time: 'bids output time' } - def __init__(self, data): + def __init__(self, bids_directory): import tarfile - import os.path - sep = os.path.sep - source_dir = data['bids_directory'] + sep + data['output_time'] # current directory - output_filename = data['bids_directory'] + sep + data['output_time'] + '.tar.gz' + output_filename = bids_directory + '.tar.gz' with tarfile.open(output_filename, "w:gz") as tar: - tar.add(source_dir, arcname=os.path.basename(source_dir)) - import platform - import subprocess - if platform.system() == 'Windows': - os.startfile(data['bids_directory']) - elif platform.system() == 'Darwin': - subprocess.Popen(['open', data['bids_directory']]) - else: - subprocess.Popen(['xdg-open', data['bids_directory']]) + tar.add(bids_directory, arcname=os.path.basename(bids_directory)) + + #import platform + #import subprocess + #if platform.system() == 'Windows': + # os.startfile(data['bids_directory']) + #elif platform.system() == 'Darwin': + # subprocess.Popen(['open', data['bids_directory']]) + #else: + # subprocess.Popen(['xdg-open', data['bids_directory']]) # Anonymize - scrubs edf header data. @@ -257,7 +254,7 @@ def to_bids(self, bids_basename.update(session=session) try: - write_raw_bids(raw, bids_basename, anonymize=dict(daysback=33630), overwrite=False, verbose=False) + 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')) diff --git a/requirements.txt b/requirements.txt index 85f997b..5708976 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,10 @@ -eventlet>=0.31.0 +eventlet~=0.31.0 mne~=0.23.0 -# MNE-BIDS 0.8 will support the .EDF extension -# Before its release we need to use the dev version -git+git://github.com/mne-tools/mne-bids@main#egg=mne-bids +mne-bids~=0.8 mne-features~=0.1 numpy~=1.19.5 python-socketio~=5.0.4 python-engineio~=4.0.0 bids-validator~=1.6.0 -pybv>=0.4 -requests>=2.25.0 \ No newline at end of file +pybv~=0.4 +requests~=2.25.0 diff --git a/src/App.js b/src/App.js index 08484d1..d86e6ef 100644 --- a/src/App.js +++ b/src/App.js @@ -84,7 +84,7 @@ const App = () => { }, }, { - title: '4) Validator', + title: '4) Validate and package', onClick: (e) => { e.preventDefault(); setActiveMenuTab(3); diff --git a/src/css/Authentication.module.css b/src/css/Authentication.module.css index 28f1ae2..d2c3aa9 100644 --- a/src/css/Authentication.module.css +++ b/src/css/Authentication.module.css @@ -6,6 +6,7 @@ cursor: default; font-size: 12px; background-color: #e9eaee; + width: 50%; } .loginMessage { font-size: 14px; diff --git a/src/css/Configuration.css b/src/css/Configuration.css index 368dca2..d0aca81 100644 --- a/src/css/Configuration.css +++ b/src/css/Configuration.css @@ -22,4 +22,9 @@ input[type="button"].primary-btn:disabled { textarea { vertical-align: top; font-family: Arial; -} \ No newline at end of file +} +.resetBtn { + text-align: right; + width: 50%; + background-color: #e9eaee; +} diff --git a/src/jsx/Configuration.js b/src/jsx/Configuration.js index fc4f7f1..6b13830 100644 --- a/src/jsx/Configuration.js +++ b/src/jsx/Configuration.js @@ -41,96 +41,56 @@ const Configuration = (props) => { const socketContext = useContext(SocketContext); // React State + const initialState = { + eegRuns: null, + edfData: [], + edfFiles: [], + modality: 'ieeg', + eventFiles: [], + annotationsTSV: [], + annotationsJSON: [], + bidsDirectory: null, + LORIScompliant: true, + siteID: 'n/a', + siteOptions: [], + siteUseAPI: false, + projectID: 'n/a', + projectOptions: [], + projectUseAPI: false, + subprojectID: 'n/a', + subprojectOptions: [], + subprojectUseAPI: false, + session: '', + sessionOptions: [], + sessionUseAPI: false, + bidsMetadataFile: [], + bidsMetadata: null, + lineFreq: 'n/a', + taskName: '', + reference: 'n/a', + recordingType: 'n/a', + participantEntryMode: 'manual', + participantCandID: '', + participantID: '', + participantDOB: null, + participantAge: 'n/a', + participantSex: 'n/a', + participantHand: 'n/a', + anonymize: false, + subjectID: '', + }; + const state = {}; - state.eegRuns = {}; - [state.eegRuns.get, state.eegRuns.set] = useState(null); - state.edfData = {}; - [state.edfData.get, state.edfData.set] = useState([]); - - state.edfFiles = {}; - [state.edfFiles.get, state.edfFiles.set] = useState([]); - state.modality = {}; - [state.modality.get, state.modality.set] = useState('ieeg'); - state.eventFiles = {}; - [state.eventFiles.get, state.eventFiles.set] = useState([]); - state.annotationsTSV = {}; - [state.annotationsTSV.get, state.annotationsTSV.set] = useState([]); - state.annotationsJSON = {}; - [state.annotationsJSON.get, state.annotationsJSON.set] = useState([]); - - state.bidsDirectory = {}; - [state.bidsDirectory.get, state.bidsDirectory.set] = useState(null); - state.LORIScompliant = {}; - [state.LORIScompliant.get, state.LORIScompliant.set] = useState(true); - state.siteID = {}; - [state.siteID.get, state.siteID.set] = useState('n/a'); - state.siteOptions = {}; - [state.siteOptions.get, state.siteOptions.set] = useState([]); - state.siteUseAPI = {}; - [state.siteUseAPI.get, state.siteUseAPI.set] = useState(false); - state.projectID = {}; - [state.projectID.get, state.projectID.set] = useState('n/a'); - state.projectOptions = {}; - [state.projectOptions.get, state.projectOptions.set] = useState([]); - state.projectUseAPI = {}; - [state.projectUseAPI.get, state.projectUseAPI.set] = useState(false); - state.subprojectID = {}; - [state.subprojectID.get, state.subprojectID.set] = useState('n/a'); - state.subprojectOptions = {}; - [state.subprojectOptions.get, state.subprojectOptions.set] = useState([]); - state.subprojectUseAPI = {}; - [state.subprojectUseAPI.get, state.subprojectUseAPI.set] = useState(false); - state.session = {}; - [state.session.get, state.session.set] = useState(''); - state.sessionOptions = {}; - 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('n/a'); - state.taskName = {}; - [state.taskName.get, state.taskName.set] = useState(''); - state.reference = {}; - [state.reference.get, state.reference.set] = useState('n/a'); - state.recordingType = {}; - [state.recordingType.get, state.recordingType.set] = useState('n/a'); - [state.sessionOptions.get, state.sessionOptions.set] = useState([]); - state.sessionUseAPI = {}; - [state.sessionUseAPI.get, state.sessionUseAPI.set] = useState(false); - state.participantEntryMode = {}; - [ - state.participantEntryMode.get, - state.participantEntryMode.set, - ] = useState('manual'); - state.participantCandID = {}; - [state.participantCandID.get, state.participantCandID.set] = useState(''); - state.participantID = {}; - [state.participantID.get, state.participantID.set] = useState(''); - state.participantDOB = {}; - [state.participantDOB.get, state.participantDOB.set] = useState(null); - state.participantAge = {}; - [state.participantAge.get, state.participantAge.set] = useState('n/a'); - state.participantSex = {}; - [state.participantSex.get, state.participantSex.set] = useState('n/a'); - state.participantHand = {}; - [state.participantHand.get, state.participantHand.set] = useState('n/a'); - state.anonymize = {}; - [state.anonymize.get, state.anonymize.set] = useState(false); - state.subjectID = {}; - [state.subjectID.get, state.subjectID.set] = useState(''); - state.authCredentialsVisible = {}; - [ - state.authCredentialsVisible.get, - state.authCredentialsVisible.set, - ] = useState(false); - state.isAuthenticated = {}; - [state.isAuthenticated.get, state.isAuthenticated.set] = useState(false); + for (const [key, value] of Object.entries(initialState)) { + state[key] = {}; + [state[key].get, state[key].set] = useState(value); + } + + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [authCredentialsVisible, setAuthCredentialsVisible] = useState(false); const [preparedBy, setPreparedBy] = useState(''); const [displayErrors, setDisplayErrors] = useState(false); - - // React State const [outputTime, setOutputTime] = useState(''); const [successMessage, setSuccessMessage] = useState(null); const [modalVisible, setModalVisible] = useState(false); @@ -156,6 +116,15 @@ const Configuration = (props) => { }, }); + /** + * reset - reset the form fields (state). + */ + const reset = () => { + for (const [key, value] of Object.entries(initialState)) { + state[key].set(value); + } + }; + /** * beginBidsCreation - create BIDS format. * Sent by socket to python: edf_to_bids. @@ -219,7 +188,7 @@ const Configuration = (props) => { }, [outputTime]); useEffect(() => { - if (state.isAuthenticated.get && state.LORIScompliant) { + if (isAuthenticated && state.LORIScompliant) { state.participantEntryMode.set('new_loris'); } }, [state.LORIScompliant.get]); @@ -492,6 +461,7 @@ const Configuration = (props) => { const validateRecordingParameters = () => { const result = []; + let invalidKeyFound = false; if (!appContext.getFromTask('bidsMetadata')) { return formatWarning('No EEG Parameter metadata file selected'); @@ -513,6 +483,7 @@ const Configuration = (props) => { Object.keys(metadata).map((key) => { if (invalidKeys.indexOf(key) > -1) { + invalidKeyFound = true; result.push(
- ⚠ - Invalid keys for the selected modality will be ignored. -
, - ); + if (invalidKeyFound) { + result.push( ++ ⚠ + Note: if invalid or extra parameters are found + in the .json file, they are ignored. +
, + ); + } return result; }; @@ -850,6 +824,7 @@ const Configuration = (props) => { }, []); useEffect(() => { + console.log(state.edfFiles.get); if (socketContext) { socketContext.emit('get_edf_data', { files: state.edfFiles.get.map((edfFile) => @@ -956,7 +931,7 @@ const Configuration = (props) => { if (data.error) { // todo display error message - login failure } else { - state.isAuthenticated.set(true); + setIsAuthenticated(true); //state.participantEntryMode.set('new_loris'); } }); @@ -1005,7 +980,7 @@ const Configuration = (props) => { appContext.setTask(name, value); break; case 'participantEntryMode': - if (state.isAuthenticated.get === false) { + if (isAuthenticated === false) { state.participantEntryMode.set('new_loris'); } else { state.participantEntryMode.set(value); @@ -1014,7 +989,7 @@ const Configuration = (props) => { case 'LORIScompliant': if (value === 'yes') { value = true; - if (state.isAuthenticated.get) { + if (isAuthenticated) { state.participantEntryMode.set('new_loris'); } else { state.participantEntryMode.set('manual'); @@ -1151,15 +1126,24 @@ const Configuration = (props) => { * @param {boolean} hidden */ const hideAuthCredentials = (hidden) => { - state.authCredentialsVisible.set(!hidden); + setAuthCredentialsVisible(!hidden); }; if (props.appMode === 'Configuration') { return ( <> -Create your BIDS output if your configuration is correct!
>, Validator: <> -Validate the BIDS output and then compress it for storage!
>, }; diff --git a/src/jsx/elements/inputs.js b/src/jsx/elements/inputs.js index ca16a2a..9adc625 100644 --- a/src/jsx/elements/inputs.js +++ b/src/jsx/elements/inputs.js @@ -15,6 +15,10 @@ export const FileInput = (props) => { const handleChange = (event) => { // Send current file to parent component const files = event.target.files ? Array.from(event.target.files) : []; + + // Clear the input file + event.target.value = null; + props.onUserInput(props.id, files); }; /**