Skip to content

Commit

Permalink
Remove MNE dependencies.
Browse files Browse the repository at this point in the history
  • Loading branch information
jackz314 committed Apr 8, 2021
1 parent ce1ffcd commit 7e7919c
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 116 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,27 @@ pip install -i https://test.pypi.org/simple/ eeglabio
### Dependencies

eeglabio requires Python >= 3.6 and the following packages:
* [mne](https://github.com/mne-tools/mne-python)
* [numpy](http://numpy.org/)
* [scipy](https://www.scipy.org/)

### Usage
For testing, we also require the following additional packages:
* [mne](https://github.com/mne-tools/mne-python)


### Example Usage (with [MNE](https://github.com/mne-tools/mne-python))

Export from MNE [`Epochs`](https://mne.tools/stable/generated/mne.Epochs.html) to EEGLAB (`.set`):
```python
import mne
from eeglabio.epochs import export_set
from eeglabio.utils import export_mne_epochs
epochs = mne.Epochs(...)
export_set(epochs, "file_name.set")
export_mne_epochs(epochs, "file_name.set")
```

Export from MNE [`Raw`](https://mne.tools/stable/generated/mne.io.Raw.html) to EEGLAB (`.set`):
```python
import mne
from eeglabio.raw import export_set
from eeglabio.utils import export_mne_raw
raw = mne.io.read_raw(...)
export_set(raw, "file_name.set")
export_mne_raw(raw, "file_name.set")
```
2 changes: 1 addition & 1 deletion eeglabio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from . import epochs
from . import raw

__all__ = [epochs, raw]
__all__ = [epochs, raw]
2 changes: 1 addition & 1 deletion eeglabio/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""The version number."""

__version__ = '0.0.1-1'
__version__ = '0.0.1-2'
82 changes: 50 additions & 32 deletions eeglabio/epochs.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,75 @@
import numpy as np
from numpy.core.records import fromarrays
from scipy.io import savemat
from .utils import _get_eeglab_full_cords

from .utils import cart_to_eeglab

def export_set(inst, fname):
"""Export Epochs to EEGLAB's .set format.

def export_set(fname, data, sfreq, events, tmin, tmax, ch_names,
event_id=None, ch_locs=None):
"""Export epoch data to EEGLAB's .set format.
Parameters
----------
inst : mne.BaseEpochs
Epochs instance to save
fname : str
Name of the export file.
data : np.ndarray, shape (n_epochs, n_channels, n_samples)
Data array containing epochs. Follows the same format as
MNE Epochs' data array.
sfreq : int
sample frequency of data
events : np.ndarray, shape (n_events, 3)
Event array, the first column contains the event time in samples,
the second column contains the value of the stim channel immediately
before the event/step, and the third column contains the event id.
Follows the same format as MNE's event arrays.
tmin : float
Start time (seconds) before event.
tmax : float
End time (seconds) after event.
ch_names : list of str
Channel names.
event_id : dict
Names of conditions corresponding to event ids (last column of events).
If None, event names will default to string versions of the event ids.
ch_locs : np.ndarray, shape (n_channels, 3)
Array containing channel locations in Cartesian coordinates (x, y, z)
Notes
-----
Channel locations are expanded to the full EEGLAB format
For more details see .io.utils.cart_to_eeglab_full_coords
"""
# load data first
inst.load_data()

# remove extra epoc and STI channels
chs_drop = [ch for ch in ['epoc', 'STI 014'] if ch in inst.ch_names]
inst.drop_channels(chs_drop)

data = inst.get_data() * 1e6 # convert to microvolts
data = data * 1e6 # convert to microvolts
data = np.moveaxis(data, 0, 2) # convert to EEGLAB 3D format
fs = inst.info["sfreq"]
times = inst.times
trials = len(inst.events) # epoch count in EEGLAB

# get full EEGLAB coordinates to export
full_coords = _get_eeglab_full_cords(inst)
trials = len(events) # epoch count in EEGLAB

ch_names = inst.ch_names
if ch_locs is not None:
# get full EEGLAB coordinates to export
full_coords = cart_to_eeglab(ch_locs)

# convert to record arrays for MATLAB format
chanlocs = fromarrays(
[ch_names, *full_coords.T, np.repeat('', len(ch_names))],
names=["labels", "X", "Y", "Z", "sph_theta", "sph_phi",
"sph_radius", "theta", "radius",
"sph_theta_besa", "sph_phi_besa", "type"])
# convert to record arrays for MATLAB format
chanlocs = fromarrays(
[ch_names, *full_coords.T, np.repeat('', len(ch_names))],
names=["labels", "X", "Y", "Z", "sph_theta", "sph_phi",
"sph_radius", "theta", "radius",
"sph_theta_besa", "sph_phi_besa", "type"])
else:
chanlocs = fromarrays([ch_names], names=["labels"])

# reverse order of event type dict to look up events faster
event_type_d = dict((v, k) for k, v in inst.event_id.items())
ev_types = [event_type_d[ev[2]] for ev in inst.events]
# name: value to value: name
if event_id:
event_type_d = dict((v, k) for k, v in event_id.items())
ev_types = [event_type_d[ev[2]] for ev in events]
else:
ev_types = [str(ev[2]) for ev in events]

# EEGLAB latency, in units of data sample points
# ev_lat = [int(n) for n in self.events[:, 0]]
ev_lat = inst.events[:, 0]
ev_lat = events[:, 0]

# event durations should all be 0 except boundaries which we don't have
ev_dur = np.zeros((trials,), dtype=np.int64)
Expand All @@ -75,14 +94,13 @@ def export_set(inst, fname):
nbchan=data.shape[0],
pnts=float(data.shape[1]),
trials=trials,
srate=fs,
xmin=times[0],
xmax=times[-1],
srate=sfreq,
xmin=tmin,
xmax=tmax,
chanlocs=chanlocs,
event=events,
epoch=epochs,
icawinv=[],
icasphere=[],
icaweights=[]))
savemat(fname, eeg_d,
appendmat=False)
savemat(fname, eeg_d, appendmat=False)
87 changes: 43 additions & 44 deletions eeglabio/raw.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,66 @@
import numpy as np
from numpy.core.records import fromarrays
from scipy.io import savemat
from .utils import _get_eeglab_full_cords

from .utils import cart_to_eeglab

def export_set(inst, fname):
"""Export Raw to EEGLAB's .set format.

def export_set(fname, data, sfreq, ch_names, ch_locs=None, annotations=None):
"""Export continuous raw data to EEGLAB's .set format.
Parameters
----------
inst : mne.io.BaseRaw
Raw instance to save
fname : str
Name of the export file.
data : np.ndarray, shape (n_epochs, n_channels, n_samples)
Data array containing epochs. Follows the same format as
MNE Epochs' data array.
sfreq : int
sample frequency of data
ch_names : list of str
Channel names.
ch_locs : np.ndarray, shape (n_channels, 3)
Array containing channel locations in Cartesian coordinates (x, y, z)
annotations : list, shape (3, n_annotations)
List containing three annotation subarrays:
first array (str) is description,
second array (float) is onset (starting time in seconds),
third array (float) is duration (in seconds)
This roughly follows MNE's Annotations structure.
Notes
-----
Channel locations are expanded to the full EEGLAB format
For more details see .utils.cart_to_eeglab_full_coords
"""
# load data first
inst.load_data()

# remove extra epoc and STI channels
chs_drop = [ch for ch in ['epoc'] if ch in inst.ch_names]
if 'STI 014' in inst.ch_names and \
not (inst.filenames[0].endswith('.fif')):
chs_drop.append('STI 014')
inst.drop_channels(chs_drop)
data = data * 1e6 # convert to microvolts

data = inst.get_data() * 1e6 # convert to microvolts
fs = inst.info["sfreq"]
times = inst.times
if ch_locs is not None:
# get full EEGLAB coordinates to export
full_coords = cart_to_eeglab(ch_locs)

# convert xyz to full eeglab coordinates
full_coords = _get_eeglab_full_cords(inst)
# convert to record arrays for MATLAB format
chanlocs = fromarrays(
[ch_names, *full_coords.T, np.repeat('', len(ch_names))],
names=["labels", "X", "Y", "Z", "sph_theta", "sph_phi",
"sph_radius", "theta", "radius",
"sph_theta_besa", "sph_phi_besa", "type"])
else:
chanlocs = fromarrays([ch_names], names=["labels"])

ch_names = inst.ch_names
eeg = dict(data=data, setname=fname, nbchan=data.shape[0],
pnts=data.shape[1], trials=1, srate=sfreq, xmin=0,
xmax=data.shape[1] / sfreq, chanlocs=chanlocs, icawinv=[],
icasphere=[], icaweights=[])

# convert to record arrays for MATLAB format
chanlocs = fromarrays(
[ch_names, *full_coords.T, np.repeat('', len(ch_names))],
names=["labels", "X", "Y", "Z", "sph_theta", "sph_phi",
"sph_radius", "theta", "radius",
"sph_theta_besa", "sph_phi_besa", "type"])
if annotations is not None:
events = fromarrays([annotations[0],
annotations[1] * sfreq + 1,
annotations[2] * sfreq],
names=["type", "latency", "duration"])
eeg['event'] = events

events = fromarrays([inst.annotations.description,
inst.annotations.onset * fs + 1,
inst.annotations.duration * fs],
names=["type", "latency", "duration"])
eeg_d = dict(EEG=dict(data=data,
setname=fname,
nbchan=data.shape[0],
pnts=data.shape[1],
trials=1,
srate=fs,
xmin=times[0],
xmax=times[-1],
chanlocs=chanlocs,
event=events,
icawinv=[],
icasphere=[],
icaweights=[]))
eeg_d = dict(EEG=eeg)

savemat(fname, eeg_d,
appendmat=False)
savemat(fname, eeg_d, appendmat=False)
2 changes: 1 addition & 1 deletion eeglabio/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import os.path as op

data_dir = op.join(op.dirname(__file__), 'data')
data_dir = op.join(op.dirname(__file__), 'data')
9 changes: 5 additions & 4 deletions eeglabio/tests/test_epochs.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from os import path as op
from pathlib import Path

import numpy as np
import pytest
from mne import read_events, pick_types, Epochs, read_epochs_eeglab
from mne.io import read_raw_fif
from os import path as op
import numpy as np
from numpy.testing import assert_allclose, assert_array_equal

from eeglabio.epochs import export_set
from eeglabio.utils import export_mne_epochs

raw_fname = Path(__file__).parent / "data" / "test_raw.fif"
event_name = Path(__file__).parent / "data" / 'test-eve.fif'
Expand All @@ -30,7 +31,7 @@ def test_export_set(tmpdir, preload):
raw.load_data()
epochs = Epochs(raw, events, preload=preload)
temp_fname = op.join(str(tmpdir), 'test_epochs.set')
export_set(epochs, temp_fname)
export_mne_epochs(epochs, temp_fname)
epochs_read = read_epochs_eeglab(temp_fname)
assert epochs.ch_names == epochs_read.ch_names
cart_coords = np.array([d['loc'][:3]
Expand Down
7 changes: 4 additions & 3 deletions eeglabio/tests/test_raw.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from pathlib import Path
from os import path as op
from pathlib import Path

import numpy as np
from mne.io import read_raw_fif, read_raw_eeglab
from numpy.testing import assert_allclose

from eeglabio.raw import export_set
from eeglabio.utils import export_mne_raw

raw_fname = Path(__file__).parent / "data" / "test_raw.fif"

Expand All @@ -14,7 +15,7 @@ def test_export_set(tmpdir):
raw = read_raw_fif(raw_fname)
raw.load_data()
temp_fname = op.join(str(tmpdir), 'test_raw.set')
export_set(raw, temp_fname)
export_mne_raw(raw, temp_fname)
raw_read = read_raw_eeglab(temp_fname, preload=True)
assert raw.ch_names == raw_read.ch_names
cart_coords = np.array([d['loc'][:3] for d in raw.info['chs']]) # just xyz
Expand Down
Loading

0 comments on commit 7e7919c

Please sign in to comment.