def __init__(self, dataset_dir, overwrite=False): print('Initializing BIDS dataset directory tree in %s' % dataset_dir) self.bids_dir = dataset_dir self.derivatives_dir = os.path.join(dataset_dir, 'derivatives') self.sourcedata_dir = os.path.join(dataset_dir, 'sourcedata') self.code_dir = os.path.join(dataset_dir, 'code') self.work_dir = os.path.join(dataset_dir, 'work') bio.safe_mkdir(self.derivatives_dir) bio.safe_mkdir(self.sourcedata_dir) bio.safe_mkdir(self.code_dir) bio.safe_mkdir(self.work_dir) self.translator_file = os.path.join(self.code_dir, 'Protocol_Translator.json') print('Creating required file templates') # README file self.readme_file = os.path.join(dataset_dir, 'README') with open(self.readme_file, 'w') as fd: fd.writelines('Useful information about this dataset\n') # CHANGES changelog file self.changes_file = os.path.join(dataset_dir, 'CHANGES') with open(self.changes_file, 'w') as fd: fd.writelines(['1.0.0 YYYY-MM-DD\n', ' - Initial release\n']) # Create template JSON dataset description (must comply with BIDS 1.2 spec) self.datadesc_json = os.path.join(self.bids_dir, 'dataset_description.json') meta_dict = dict({ 'Name': 'Descriptive name for this dataset', 'BIDSVersion': '1.2', 'License': 'This data is made available under the Creative Commons BY-SA 4.0 International License.', 'Authors': ['First Author', 'Second Author'], 'Acknowledgments': 'Thanks to everyone for all your help', 'HowToAcknowledge': 'Please cite: Author AB, Seminal Paper Title, High Impact Journal, 2019', 'Funding': ['First Grant', 'Second Grant'], 'ReferencesAndLinks': ['A Reference', 'Another Reference', 'A Link'], 'DatasetDOI': '10.0.1.2/abcd.10' }) bio.write_json(self.datadesc_json, meta_dict, overwrite) # Create participants JSON file defining columns in participants.tsv # See self.participants_json = os.path.join(self.bids_dir, 'participants.json') meta_dict = dict({ 'age': { 'Description': 'Age of participant', 'Units': 'years' }, 'sex': { 'Description': 'Sex of participant', 'Levels': { 'M': 'male', 'F': 'female', 'T': 'transgender' } }, 'group': { 'Description': 'participant group assignment' }, }) bio.write_json(self.participants_json, meta_dict, overwrite) # Create .bidsignore file to skip work/ during validation self.ignore_file = os.path.join(dataset_dir, '.bidsignore') with open(self.ignore_file, 'w') as fd: fd.writelines('work/\n')
def organize_series(conv_dir, first_pass, prot_dict, src_dir, sid, ses, clean_conv_dir, overwrite=False): """ Organize dcm2niix output into BIDS subject/session directory :param conv_dir: string Working conversion directory :param first_pass: boolean Flag for first pass conversion :param prot_dict: dictionary Protocol translation dictionary :param src_dir: string BIDS source output subj or subj/session directory :param sid: string subject ID :param ses: string session name or number :param clean_conv_dir: bool clean up conversion directory :param overwrite: bool overwrite flag :return: """ # Flag for working conversion directory cleanup do_cleanup = clean_conv_dir # Proceed if conversion directory exists if os.path.isdir(conv_dir): # Get Nifti file list ordered by acquisition time nii_list, json_list, acq_times = btr.ordered_file_list(conv_dir) # Infer run numbers accounting for duplicates. # Only used if run-* not present in translator BIDS filename stub if not first_pass: run_no = btr.auto_run_no(nii_list, prot_dict) # Loop over all Nifti files (*.nii, *.nii.gz) for this subject for fc, src_nii_fname in enumerate(nii_list): # Parse image filename into fields info = bio.parse_dcm2niix_fname(src_nii_fname) # Check if we're creating new protocol dictionary if first_pass: print(' Adding protocol %s to dictionary template' % info['SerDesc']) # Add current protocol to protocol dictionary # Use default EXCLUDE_* values which can be changed (or not) by the user prot_dict[info['SerDesc']] = [ "EXCLUDE_BIDS_Directory", "EXCLUDE_BIDS_Name", "UNASSIGNED" ] else: # JSON sidecar for this image src_json_fname = json_list[fc] # Warn if not found and continue if not os.path.isfile(src_json_fname): print('* WARNING: JSON sidecar %s not found' % src_json_fname) continue if info['SerDesc'] in prot_dict.keys(): if prot_dict[info['SerDesc']][0].startswith('EXCLUDE'): # Skip excluded protocols print('* Excluding protocol ' + str(info['SerDesc'])) else: print(' Organizing ' + str(info['SerDesc'])) # Use protocol dictionary to determine purpose folder, BIDS filename suffix and fmap linking bids_purpose, bids_suffix, bids_intendedfor = prot_dict[ info['SerDesc']] # Safely add run-* key to BIDS suffix bids_suffix = btr.add_run_number( bids_suffix, run_no[fc]) # Assume the IntendedFor field should aslo have a run- added prot_dict = btr.add_intended_run( prot_dict, info, run_no[fc]) # Create BIDS purpose directory bids_purpose_dir = os.path.join(src_dir, bids_purpose) bio.safe_mkdir(bids_purpose_dir) # Complete BIDS filenames for image and sidecar if ses: bids_prefix = 'sub-' + sid + '_ses-' + ses + '_' else: bids_prefix = 'sub-' + sid + '_' # Construct BIDS source Nifti and JSON filenames bids_nii_fname = os.path.join( bids_purpose_dir, bids_prefix + bids_suffix + '.nii.gz') bids_json_fname = bids_nii_fname.replace( '.nii.gz', '.json') # Add prefix and suffix to IntendedFor values if 'UNASSIGNED' not in bids_intendedfor: if isinstance(bids_intendedfor, str): # Single linked image bids_intendedfor = btr.build_intendedfor( sid, ses, bids_intendedfor) else: # Loop over all linked images for ifc, ifstr in enumerate(bids_intendedfor): # Avoid multiple substitutions if '.nii.gz' not in ifstr: bids_intendedfor[ ifc] = btr.build_intendedfor( sid, ses, ifstr) # Special handling for specific purposes (anat, func, fmap, etc) # This function populates BIDS structure with the image and adjusted sidecar btr.purpose_handling(bids_purpose, bids_intendedfor, info['SeqName'], src_nii_fname, src_json_fname, bids_nii_fname, bids_json_fname, overwrite) else: # Skip protocols not in the dictionary print('* Protocol ' + str(info['SerDesc']) + ' is not in the dictionary, did not convert.') if not first_pass: # Optional working directory cleanup after Pass 2 if do_cleanup: print(' Cleaning up temporary files') shutil.rmtree(conv_dir) else: print(' Preserving conversion directory')
def organize_series(conv_dir, first_pass, prot_dict, src_dir, sid, ses, clean_conv_dir, overwrite=False): """ Organize dcm2niix output into BIDS subject/session directory :param conv_dir: string Working conversion directory :param first_pass: boolean Flag for first pass conversion :param prot_dict: dictionary Protocol translation dictionary :param src_dir: string BIDS source output subj or subj/session directory :param sid: string subject ID :param ses: string session name or number :param clean_conv_dir: bool clean up conversion directory :param overwrite: bool overwrite flag :return: """ # Flag for working conversion directory cleanup do_cleanup = clean_conv_dir # Proceed if conversion directory exists if os.path.isdir(conv_dir): # Get Nifti file list ordered by acquisition time nii_list, json_list, acq_times = btr.ordered_file_list(conv_dir) # Infer run numbers accounting for duplicates. # Only used if run-* not present in translator BIDS filename stub if not first_pass: run_no = btr.auto_run_no(nii_list, prot_dict) # Loop over all Nifti files (*.nii, *.nii.gz) for this subject for fc, src_nii_fname in enumerate(nii_list): # Parse image filename into fields info = bio.parse_dcm2niix_fname(src_nii_fname) # Check if we're creating new protocol dictionary if first_pass: print(' Adding protocol %s to dictionary template' % info['SerDesc']) # Add current protocol to protocol dictionary # Use default EXCLUDE_* values which can be changed (or not) by the user prot_dict[info['SerDesc']] = ["EXCLUDE_BIDS_Directory", "EXCLUDE_BIDS_Name", "UNASSIGNED"] else: # JSON sidecar for this image src_json_fname = json_list[fc] # Warn if not found and continue if not os.path.isfile(src_json_fname): print('* WARNING: JSON sidecar %s not found' % src_json_fname) continue if info['SerDesc'] in prot_dict.keys(): if prot_dict[info['SerDesc']][0].startswith('EXCLUDE'): # Skip excluded protocols print('* Excluding protocol ' + str(info['SerDesc'])) else: print(' Organizing ' + str(info['SerDesc'])) # Use protocol dictionary to determine purpose folder, BIDS filename suffix and fmap linking bids_purpose, bids_suffix, bids_intendedfor = prot_dict[info['SerDesc']] # Safely add run-* key to BIDS suffix bids_suffix = btr.add_run_number(bids_suffix, run_no[fc]) # Assume the IntendedFor field should aslo have a run- added prot_dict = btr.add_intended_run(prot_dict, info, run_no[fc]) # Create BIDS purpose directory bids_purpose_dir = os.path.join(src_dir, bids_purpose) bio.safe_mkdir(bids_purpose_dir) # Complete BIDS filenames for image and sidecar if ses: bids_prefix = 'sub-' + sid + '_ses-' + ses + '_' else: bids_prefix = 'sub-' + sid + '_' # Construct BIDS source Nifti and JSON filenames bids_nii_fname = os.path.join(bids_purpose_dir, bids_prefix + bids_suffix + '.nii.gz') bids_json_fname = bids_nii_fname.replace('.nii.gz', '.json') # Add prefix and suffix to IntendedFor values if 'UNASSIGNED' not in bids_intendedfor: if isinstance(bids_intendedfor, str): # Single linked image bids_intendedfor = btr.build_intendedfor(sid, ses, bids_intendedfor) else: # Loop over all linked images for ifc, ifstr in enumerate(bids_intendedfor): # Avoid multiple substitutions if '.nii.gz' not in ifstr: bids_intendedfor[ifc] = btr.build_intendedfor(sid, ses, ifstr) # Special handling for specific purposes (anat, func, fmap, etc) # This function populates BIDS structure with the image and adjusted sidecar btr.purpose_handling(bids_purpose, bids_intendedfor, info['SeqName'], src_nii_fname, src_json_fname, bids_nii_fname, bids_json_fname, overwrite) else: # Skip protocols not in the dictionary print('* Protocol ' + str(info['SerDesc']) + ' is not in the dictionary, did not convert.') if not first_pass: # Optional working directory cleanup after Pass 2 if do_cleanup: print(' Cleaning up temporary files') shutil.rmtree(conv_dir) else: print(' Preserving conversion directory')