diff --git a/.coveragerc b/.coveragerc index 1cedb8b..1c315c8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,4 +4,5 @@ omit = */setup.py */tests/* */notebooks/* - */tinnsleep/config.py \ No newline at end of file + */tinnsleep/config.py + */tinnsleep/external/* diff --git a/.gitignore b/.gitignore index a5b6d36..f79dd29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ #Notebook draft drafts/ Notebooks/*_vbrouillon.* +tests/dummy_fif.edf # Byte-compiled / optimized / DLL files results/ diff --git a/tests/dummy_fif.edf b/tests/dummy_fif.edf new file mode 100644 index 0000000..c0c7461 Binary files /dev/null and b/tests/dummy_fif.edf differ diff --git a/tests/dummy_fif.fif b/tests/dummy_fif.fif new file mode 100644 index 0000000..341baac Binary files /dev/null and b/tests/dummy_fif.fif differ diff --git a/tests/test_data.py b/tests/test_data.py index b05bcf4..f847d28 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -282,5 +282,3 @@ def test_read_etiology_file_real(): data_info_file = os.path.join(os.path.dirname(__file__), "../notebooks/data/data_info.csv") data_info = pd.read_csv(data_info_file, sep=";") data_info.merge(df_etiology, on="subject") - - diff --git a/tests/test_io.py b/tests/test_io.py new file mode 100644 index 0000000..98c021e --- /dev/null +++ b/tests/test_io.py @@ -0,0 +1,35 @@ +import mne +import os +import numpy as np +import numpy.testing as npt +from tinnsleep.external.save_edf import write_edf +import matplotlib.pyplot as plt + +def test_fif2edf(): + fname = "./dummy_fif" + fname_edf = fname +".edf" + raw = mne.io.read_raw_fif(fname+".fif", preload=True) + + # plt.close("all") + # plt.figure() + # raw.plot() + # plt.show() + + signals = raw.get_data() + N, T = signals.shape + scaling = np.ones((signals.shape[0])) + scaling[np.median(np.abs(signals), axis=1)< 1e-3] = 1e6 + #raw.info["ch_names"] + if os.path.exists(fname_edf): + os.remove(fname_edf) + write_edf(raw, fname_edf) + + raw2 = mne.io.read_raw_edf(fname_edf) + signals2 = raw2.get_data() + is_uV = np.median(np.abs(signals), axis=1)< 1e-3 + npt.assert_allclose(signals2[is_uV,:T], signals[is_uV, :], atol=0.01) + npt.assert_allclose(signals2[~is_uV,:T], signals[~is_uV, :], atol=0.01) + + # plt.figure() + # raw2.plot() + # plt.show() diff --git a/tinnsleep/data.py b/tinnsleep/data.py index 01a52be..cfec87b 100644 --- a/tinnsleep/data.py +++ b/tinnsleep/data.py @@ -451,3 +451,5 @@ def read_etiology_file(etiology_file, df_etiology[new_key] = df_raw[key].replace(mapping) == 1 return df_etiology + + diff --git a/tinnsleep/external/requirements.txt b/tinnsleep/external/requirements.txt new file mode 100644 index 0000000..761cd44 --- /dev/null +++ b/tinnsleep/external/requirements.txt @@ -0,0 +1,2 @@ +- mne +- pyedflib \ No newline at end of file diff --git a/tinnsleep/external/save_edf.py b/tinnsleep/external/save_edf.py new file mode 100644 index 0000000..a27653c --- /dev/null +++ b/tinnsleep/external/save_edf.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 5 12:56:31 2018 + +@author: skjerns + +Gist to save a mne.io.Raw object to an EDF file using pyEDFlib +(https://github.com/holgern/pyedflib) + +Disclaimer: + - Saving your data this way will result in slight + loss of precision (magnitude +-1e-09). + - It is assumed that the data is presented in Volt (V), it will be internally converted to microvolt + - Saving to BDF can be done by changing the file_type variable. + Be aware that you also need to change the dmin and dmax to + the corresponding minimum and maximum integer values of the + file_type: e.g. BDF+ dmin, dmax =- [-8388608, 8388607] +""" + +import pyedflib # pip install pyedflib +from datetime import datetime +import mne +import os +import numpy as np + + +def write_edf(mne_raw, fname, picks=None, tmin=0, tmax=None, overwrite=False): + """ + Saves the raw content of an MNE.io.Raw and its subclasses to + a file using the EDF+ filetype + pyEDFlib is used to save the raw contents of the RawArray to disk + + Parameters + ---------- + mne_raw : mne.io.Raw + An object with super class mne.io.Raw that contains the data + to save + fname : string + File name of the new dataset. This has to be a new filename + unless data have been preloaded. Filenames should end with .edf + picks : array-like of int | None + Indices of channels to include. If None all channels are kept. + tmin : float | None + Time in seconds of first sample to save. If None first sample + is used. + tmax : float | None + Time in seconds of last sample to save. If None last sample + is used. + overwrite : bool + If True, the destination file (if it exists) will be overwritten. + If False (default), an error will be raised if the file exists. + """ + if not issubclass(type(mne_raw), mne.io.BaseRaw): + raise TypeError('Must be mne.io.Raw type') + if not overwrite and os.path.exists(fname): + raise OSError('File already exists. No overwrite.') + # static settings + file_type = pyedflib.FILETYPE_EDFPLUS + sfreq = mne_raw.info['sfreq'] + date = datetime.now()#.strftime('%d %b %Y %H:%M:%S') + first_sample = int(sfreq * tmin) + last_sample = int(sfreq * tmax) if tmax is not None else None + + # convert data + channels = mne_raw.get_data(picks, + start=first_sample, + stop=last_sample) + + # convert to microvolts to scale up precision + #is_micro = np.median(channels, axis=1) < 1e-3 + + #channels *= 1e6 + + # set conversion parameters + dmin, dmax = [-32768, 32767] + pmin, pmax = [channels.min(), channels.max()] + n_channels = len(channels) + + # create channel from this + try: + f = pyedflib.EdfWriter(fname, + n_channels=n_channels, + file_type=file_type) + + channel_info = [] + data_list = [] + + for i in range(n_channels): + if np.median(np.abs(channels[i])) < 1e-5: + scale = 1e9 + unit = 'nV' + else: + if np.median(np.abs(channels[i])) < 1e-3: + scale = 1e6 + unit = 'uV' + else: + scale = 1 + unit = 'V' + + ch_dict = {'label': mne_raw.ch_names[i], + 'dimension': unit, + 'sample_rate': sfreq, + 'physical_min': pmin, + 'physical_max': pmax, + 'digital_min': dmin, + 'digital_max': dmax, + 'transducer': '', + 'prefilter': ''} + + channel_info.append(ch_dict) + data_list.append(scale*channels[i]) + + f.setTechnician('mne-gist-save-edf-skjerns') + f.setSignalHeaders(channel_info) + f.setStartdatetime(date) + f.writeSamples(data_list) + except Exception as e: + print(e) + return False + finally: + f.close() + return True \ No newline at end of file diff --git a/tinnsleep/io.py b/tinnsleep/io.py new file mode 100644 index 0000000..d3c1739 --- /dev/null +++ b/tinnsleep/io.py @@ -0,0 +1,4 @@ +from tinnsleep.external.save_edf import write_edf + +def fif2edf(file): + return False \ No newline at end of file