-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ENH] Add a bad channel detection method using LOF algorithm (#66)
Co-authored-by: Nicolas Barascud <10333715+nbara@users.noreply.github.com>
- Loading branch information
1 parent
5e95ee5
commit 30de2e7
Showing
9 changed files
with
171 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
meegkit.lof | ||
=========== | ||
|
||
.. automodule:: meegkit.lof | ||
|
||
.. rubric:: Functions | ||
|
||
.. autosummary:: | ||
|
||
LOF | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
"""M/EEG denoising utilities in python.""" | ||
__version__ = '0.1.3' | ||
|
||
from . import asr, cca, detrend, dss, sns, star, ress, trca, tspca, utils | ||
from . import asr, cca, detrend, dss, lof, sns, star, ress, trca, tspca, utils | ||
|
||
__all__ = ['asr', 'cca', 'detrend', 'dss', 'ress', 'sns', 'star', 'trca', | ||
__all__ = ['asr', 'cca', 'detrend', 'dss', 'lof', 'ress', 'sns', 'star', 'trca', | ||
'tspca', 'utils'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
"""Local Outlier Factor (LOF).""" | ||
# Authors: Velu Prabhakar Kumaravel <vkumaravel@fbk.eu> | ||
# License: BSD-3-Clause | ||
|
||
import logging | ||
from sklearn.neighbors import LocalOutlierFactor | ||
|
||
|
||
class LOF(): | ||
"""Local Outlier Factor. | ||
Local Outlier Factor (LOF) is an automatic, density-based outlier detection | ||
algorithm based on [1]_ and [2]_. | ||
Parameters | ||
---------- | ||
n_neighbours : int | ||
Number of neighbours defining the local neighbourhood. | ||
metric: str in {'euclidean', 'nan_euclidean', 'cosine', | ||
'cityblock', 'manhattan'} | ||
Metric to use for distance computation. Default is “euclidean” | ||
threshold : float | ||
Threshold to define outliers. Theoretical threshold ranges anywhere | ||
between 1.0 and any integer. Default: 1.5 | ||
Notes | ||
----- | ||
It is recommended to perform a CV (e.g., 10-fold) on training set to | ||
calibrate this parameter for the given M/EEG dataset. | ||
See [2]_ for details. | ||
References | ||
---------- | ||
.. [1] Breunig M, Kriegel HP, Ng RT, Sander J. | ||
2000. LOF: identifying density-based local outliers. | ||
SIGMOD Rec. 29, 2, 93-104. https://doi.org/10.1145/335191.335388 | ||
.. [2] Kumaravel VP, Buiatti M, Parise E, Farella E. | ||
2022. Adaptable and Robust EEG Bad Channel Detection Using | ||
Local Outlier Factor (LOF). Sensors (Basel). 2022 Sep 27;22(19):7314. | ||
doi: 10.3390/s22197314. PMID: 36236413; PMCID: PMC9571252. | ||
""" | ||
|
||
def __init__(self, n_neighbors=20, metric='euclidean', | ||
threshold=1.5, **kwargs): | ||
|
||
self.n_neighbors = n_neighbors | ||
self.metric = metric | ||
self.threshold = threshold | ||
|
||
def predict(self, X): | ||
"""Detect bad channels using Local Outlier Factor algorithm. | ||
Parameters | ||
---------- | ||
X : array, shape=(n_channels, n_samples) | ||
The data X should have been high-pass filtered. | ||
Returns | ||
------- | ||
bad_channel_indices : Detected bad channel indices. | ||
""" | ||
if X.ndim == 3: # in case the input data is epoched | ||
logging.warning('Expected input data with shape ' | ||
'(n_channels, n_samples)') | ||
return [] | ||
|
||
if self.n_neighbors >= X.shape[0]: | ||
logging.warning('Number of neighbours cannot be greater than the ' | ||
'number of channels') | ||
return [] | ||
|
||
if self.threshold < 1.0: | ||
logging.warning('Invalid threshold. Try a positive integer >= 1.0') | ||
return [] | ||
|
||
clf = LocalOutlierFactor(self.n_neighbors) | ||
logging.debug('[LOF] Predicting bad channels') | ||
clf.fit_predict(X) | ||
lof_scores = clf.negative_outlier_factor_ | ||
bad_channel_indices = -lof_scores >= self.threshold | ||
|
||
return bad_channel_indices |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
"""LOF test.""" | ||
import os | ||
|
||
import numpy as np | ||
import pytest | ||
import scipy.io as sio | ||
|
||
from meegkit.lof import LOF | ||
|
||
np.random.seed(9) | ||
|
||
# Data files | ||
THIS_FOLDER = os.path.dirname(os.path.abspath(__file__)) # data folder of MEEGKIT | ||
|
||
|
||
@pytest.mark.parametrize(argnames='n_neighbors', argvalues=(8, 20, 40, 2048)) | ||
def test_lof(n_neighbors, show=False): | ||
mat = sio.loadmat(os.path.join(THIS_FOLDER, 'data', 'lofdata.mat')) | ||
X = mat['X'] | ||
lof = LOF(n_neighbors) | ||
bad_channel_indices = lof.predict(X) | ||
print(bad_channel_indices) | ||
|
||
@pytest.mark.parametrize(argnames='metric', | ||
argvalues=('euclidean', 'nan_euclidean', | ||
'cosine', 'cityblock', 'manhattan')) | ||
def test_lof2(metric, show=False): | ||
mat = sio.loadmat(os.path.join(THIS_FOLDER, 'data', 'lofdata.mat')) | ||
X = mat['X'] | ||
lof = LOF(20, metric) | ||
bad_channel_indices = lof.predict(X) | ||
print(bad_channel_indices) | ||
|
||
if __name__ == "__main__": | ||
pytest.main([__file__]) | ||
#test_lof(20, True) | ||
#test_lof(metric='euclidean') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters