def copyfile_brainvision(vhdr_src, vhdr_dest, anonymize=None, verbose=False): """Copy a BrainVision file triplet to a new location and repair links. The BrainVision file format consists of three files: .vhdr, .eeg, and .vmrk The .eeg and .vmrk files associated with the .vhdr file will be given names as in `vhdr_dest` with adjusted extensions. Internal file pointers will be fixed. Parameters ---------- vhdr_src : str The src path of the .vhdr file to be copied. vhdr_dest : str The destination path of the .vhdr file. anonymize : dict | None If None (default), no anonymization is performed. If dict, data will be anonymized depending on the keys provided with the dict: `daysback` is a required key, `keep_his` is an optional key. `daysback` : int Number of days by which to move back the recording date in time. In studies with multiple subjects the relative recording date differences between subjects can be kept by using the same number of `daysback` for all subject anonymizations. `daysback` should be great enough to shift the date prior to 1925 to conform with BIDS anonymization rules. `keep_his` : bool By default (False), all subject information next to the recording date will be overwritten as well. If True, keep subject information apart from the recording date. verbose : bool Determine whether results should be logged. Defaults to False. See Also -------- mne.io.anonymize_info """ # Get extenstion of the brainvision file fname_src, ext_src = _parse_ext(vhdr_src) fname_dest, ext_dest = _parse_ext(vhdr_dest) if ext_src != ext_dest: raise ValueError(f'Need to move data with same extension' f' but got "{ext_src}", "{ext_dest}"') eeg_file_path, vmrk_file_path = _get_brainvision_paths(vhdr_src) # extract encoding from brainvision header file, or default to utf-8 enc = _get_brainvision_encoding(vhdr_src, verbose) # Copy data .eeg ... no links to repair sh.copyfile(eeg_file_path, fname_dest + '.eeg') # Write new header and marker files, fixing the file pointer links # For that, we need to replace an old "basename" with a new one # assuming that all .eeg, .vhdr, .vmrk share one basename __, basename_src = op.split(fname_src) assert basename_src + '.eeg' == op.split(eeg_file_path)[-1] assert basename_src + '.vmrk' == op.split(vmrk_file_path)[-1] __, basename_dest = op.split(fname_dest) search_lines = ['DataFile=' + basename_src + '.eeg', 'MarkerFile=' + basename_src + '.vmrk'] with open(vhdr_src, 'r', encoding=enc) as fin: with open(vhdr_dest, 'w', encoding=enc) as fout: for line in fin.readlines(): if line.strip() in search_lines: line = line.replace(basename_src, basename_dest) fout.write(line) with open(vmrk_file_path, 'r', encoding=enc) as fin: with open(fname_dest + '.vmrk', 'w', encoding=enc) as fout: for line in fin.readlines(): if line.strip() in search_lines: line = line.replace(basename_src, basename_dest) fout.write(line) if anonymize is not None: raw = read_raw_brainvision(vhdr_src, preload=False, verbose=0) daysback, keep_his = _check_anonymize(anonymize, raw, '.vhdr') raw.info = anonymize_info(raw.info, daysback=daysback, keep_his=keep_his) _anonymize_brainvision(fname_dest + '.vhdr', date=raw.info['meas_date']) if verbose: for ext in ['.eeg', '.vhdr', '.vmrk']: _, fname = os.path.split(fname_dest + ext) dirname = op.dirname(op.realpath(vhdr_dest)) print(f'Created "{fname}" in "{dirname}".') if anonymize: print('Anonymized all dates in VHDR and VMRK.')
def copyfile_edf(src, dest, anonymize=None): """Copy an EDF, EDF+, or BDF file to a new location, optionally anonymize. .. warning:: EDF/EDF+/BDF files contain two fields for recording dates: A generic "startdate" field that supports only 2-digit years, and a "Startdate" field as part of the "local recording identification", which supports 4-digit years. If you want to anonymize your file, MNE-BIDS will set the "startdate" field to 85 (i.e., 1985), the earliest possible date for that field. However, the "Startdate" field in the file's "local recording identification" and the date in the session's corresponding ``scans.tsv`` will be set correctly according to the argument provided to the ``anonymize`` parameter. Note that it is possible that not all EDF/EDF+/BDF reading software parses the accurate recording date, and that for some reading software, the wrong year (1985) may be parsed. Parameters ---------- src : str | pathlib.Path The source path of the .edf or .bdf file to be copied. dest : str | pathlib.Path The destination path of the .edf or .bdf file. anonymize : dict | None If None (default), no anonymization is performed. If dict, data will be anonymized depending on the keys provided with the dict: `daysback` is a required key, `keep_his` is an optional key. `daysback` : int Number of days by which to move back the recording date in time. In studies with multiple subjects the relative recording date differences between subjects can be kept by using the same number of `daysback` for all subject anonymizations. `daysback` should be great enough to shift the date prior to 1925 to conform with BIDS anonymization rules. Due to limitations of the EDF/BDF format, the year of the anonymized date will always be set to 1985 in the 'startdate' field of the file. The correctly-shifted year will be written to the 'local recording identification' region of the file header, which may not be parsed by all EDF/EDF+/BDF reader softwares. `keep_his` : bool By default (False), all subject information next to the recording date will be overwritten as well. If True, keep subject information apart from the recording date. Participant names and birthdates will always be anonymized if present, regardless of this setting. See Also -------- mne.io.anonymize_info copyfile_brainvision copyfile_bti copyfile_ctf copyfile_eeglab copyfile_kit """ # Ensure source & destination extensions are the same fname_src, ext_src = _parse_ext(src) fname_dest, ext_dest = _parse_ext(dest) if ext_src != ext_dest: raise ValueError(f'Need to move data with same extension, ' f' but got "{ext_src}" and "{ext_dest}"') # Copy data prior to any anonymization sh.copyfile(src, dest) # Anonymize EDF/BDF data, if requested if anonymize is not None: if ext_src == '.bdf': raw = read_raw_bdf(dest, preload=False, verbose=0) elif ext_src == '.edf': raw = read_raw_edf(dest, preload=False, verbose=0) else: raise ValueError('Unsupported file type ({0})'.format(ext_src)) # Get subject info, recording info, and recording date with open(dest, 'rb') as f: f.seek(8) # id_info field starts 8 bytes in id_info = f.read(80).decode('ascii').rstrip() rec_info = f.read(80).decode('ascii').rstrip() # Parse metadata from file if len(id_info) == 0 or len(id_info.split(' ')) != 4: id_info = "X X X X" if len(rec_info) == 0 or len(rec_info.split(' ')) != 5: rec_info = "Startdate X X X X" pid, sex, birthdate, name = id_info.split(' ') start_date, admin_code, tech, equip = rec_info.split(' ')[1:5] # Try to anonymize the recording date daysback, keep_his = _check_anonymize(anonymize, raw, '.edf') anonymize_info(raw.info, daysback=daysback, keep_his=keep_his) start_date = '01-JAN-1985' meas_date = '01.01.85' # Anonymize ID info and write to file if keep_his: # Always remove participant birthdate and name to be safe id_info = [pid, sex, "X", "X"] rec_info = ["Startdate", start_date, admin_code, tech, equip] else: id_info = ["0", "X", "X", "X"] rec_info = ["Startdate", start_date, "X", "mne-bids_anonymize", "X"] with open(dest, 'r+b') as f: f.seek(8) # id_info field starts 8 bytes in f.write(bytes(" ".join(id_info).ljust(80), 'ascii')) f.write(bytes(" ".join(rec_info).ljust(80), 'ascii')) f.write(bytes(meas_date, 'ascii'))