Giter VIP home page Giter VIP logo

mne-hfo's Introduction

Codecov test_suite CircleCI License Code style: black PyPI - Python Version Documentation Status PyPI Download count

MNE-HFO

MNE-HFO is a Python package that computes estimates of high-frequency oscillations in iEEG data stored in the BIDS-compatible datasets with the help of MNE-Python.

NOTE: This is currently in ALPHA stage, and we are looking for contributors. Please get in touch via Issues tab if you would like to contribute.

High frequency oscillations in epilepsy

A few notes that are worthy of reading. The initial papers on HFOs (Staba et al.) actually only observed HFOs on Hippocampus. In addition, the papers cited that are implemented all selected data before developing their algorithm (i.e. selected channels with HFOs).

It is also noted that the Hilbert detector was used to show HFOs exist in normal brain function, possibly unassociated with the epileptogenic zone.

Why?

Currently HFO detection and algorithms are segmented in Matlab files, which are sometimes not open-source, or possibly difficult to use. In addition, validation of HFO algorithms depend on i) sharing the algorithms ii) sharing the results with others in a readable format and iii) comparing algorithms against each other on the same dataset.

MNE-HFO links BIDS, MNE-Python and iEEG HFO event detection with the goal to make HFO detection more transparent, more robust, and facilitate data and code sharing with co-workers and collaborators.

Installation

Installation can be done via a python virtual environment, using pipenv. The package is hosted on pypi, which can be installed via pip, or pipenv. For additional installation instructions, see CONTRIBUTING.md document.

pip install mne-hfo

or

pipenv install mne-hfo

Note: Installation has been tested on MacOSX and Ubuntu, but should probably work on Windows too.

Documentation and Usage

The documentation can be found under the following links:

Note: Functionality has been tested on MacOSX and Ubuntu.

Basic Working Example

A basic working example is listed here, assuming one has loaded in a mne-Python Raw object already.

from mne_hfo import RMSDetector
detector = RMSDetector()

# assume user has loaded in raw iEEG data using mne-python
detector.fit(raw)

# get the HFO events as an *events.tsv style dataframe
hfo_event_df = detector.hfo_event_df

# get the HFO events as an *events.tsv style dataframe
hfo_annot_df = detector.hfo_df

All output to *events.tsv BIDS-compliant files will look like the following:

onset duration sample trial_type
1 3 1000 hfo_A2-A1

which will imply that there is an HFO detected using a bipolar referencing at channel A2-A1 at 1 second with duration of 3 seconds. The onset sample occurs at sample 1000 (thus sfreq is 1000 Hz). If a monopolar referencing is used, then the trial_type might be hfo_A2 to imply that an HFO was detected at channel A2.

Alternatively, one can output the data in the form of a derivatives Annotations DataFrame, which is the RECOMMENDED way. Outputting data according to BIDS Extension Proposal 21, instead would result in an *annotations.tsv file.

onset duration label channels
1 3 hfo A2-A1

with a corresponding *annotations.json file.

{
    'IntendedFor': sub-01/ses-01/eeg/sub-01_ses-01_task-01_eeg.<ext>,
    'Description': 'Automatic annotations of HFO events using mne-hfo.',
}

Optimizing Hyperparameters

In all MNE-HFO HFO detectors, we assume that there are hyper-parameters specified by the proposed algorithm. These hyper-parameters can be tuned automatically using the scikit-learn API for GridSearchCV.

from sklearn.metrics import make_scorer
from sklearn.model_selection import GridSearchCV
from mne_hfo.score import accuracy
from mne_hfo.sklearn import make_Xy_sklearn, DisabledCV

# define hyperparameter grid to search over
parameters = {'threshold': [1, 2, 3], 'win_size': [50, 100, 250]}

# define HFO detector
detector = LineLengthDetector()

# define a scoring function 
scorer = make_scorer(accuracy)

# we don't use cross-validation since the
# HFO algorithm is deterministic
cv = DisabledCV()

# instantiate the GridSearch object
gs = GridSearchCV(detector, param_grid=parameters, scoring=scorer,
                  cv=cv, refit=False, verbose=True)

# load in raw data
# raw = <load_in_raw_data>

# load in HFO annotations
# annot_df = <load_in_annotations>

# make sklearn compatible
raw_df, y = make_Xy_sklearn(raw, annot_df)

# run hyperparameter tuning based on accuracy score
gs.fit(raw_df, y, groups=None)

# show the results
print(gs.cv_results_["mean_test_score"])

In the above example, to load in raw data, one can use mne-bids and to load in the annotations dataframe, one can check out our API for different ways of doing so.

Citing

For testing and demo purposes, we use the dataset in [1]. If you use the demo/testing dataset, please cite that paper. If you use mne-hfo itself in your research, please cite the paper (TBD).

Adam Li. (2021, February 1). MNE-HFO: An open-source Python implementation of HFO detection algorithms (Version 0.0.1). Zenodo. http://doi.org/10.5281/zenodo.4485036

History and state of development

The initial code was adapted and taken from: https://gitlab.com/icrc-bme/epycom to turn into a sklearn-compatible API that works with mne-python. Additional algorithms and functionality were added.

References

[1] Fedele T, Burnos S, Boran E, Krayenbühl N, Hilfiker P, Grunwald T, Sarnthein J. Resection of high frequency oscillations predicts seizure outcome in the individual patient. Scientific Reports. 2017;7(1):13836. https://www.nature.com/articles/s41598-017-13064-1 doi:10.1038/s41598-017-13064-1

mne-hfo's People

Contributors

adam2392 avatar pmyers16 avatar stuart-2020 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mne-hfo's Issues

More detectors: MorphologyDetector

Describe the problem

These are detectors that kind of have some open-source matlab code, so we can make an attempt at porting this over into mne-hfo.

Describe your solution

Import over morphologydetector: https://github.com/HFO-detect/HFO-detect-matlab/blob/master/hfo-detect-toolbox/Morphology%20Detector/func_doMorphologyDetector.m

Describe possible alternatives

Both detectors seem to have a very large number of hyperparameters, so it might be difficult to match them perfectly.

Calculate Coincidence between two different detections from dictionaries

Describe the problem

Certain analyses split HFOs into multiple categories (i.e. ripples between 80-250Hz and fast ripples between 250-500Hz). For the LineLength and RMS detectors, this needs to be accomplished by running the detector twice, once in each frequency band. We will want the ability to compare the different runs, specifically the coincidence between those runs. Fedele et al suggests that the HFOs where there is simultaneous ripple and fast ripple are what are most informative.

Describe your solution

Method that returns some data structure containing the events that occur simultaneously between the two runs. If we want the coincidence of ripples and fast ripples (RMS detector for example), we can do:

data = raw...
ripple_kwargs = {
  filter_band = (80, 250),
  ...
}
rippleDetector = RMSDetector(**ripple_kwargs)
ripple_dict = rippleDetector.fit(data).chs_hfos_
fast_ripple_kwargs = {
  filter_band = (250, 500),
  ...
}
fastRippleDetector = RMSDetector(**fast_ripple_kwargs)
fast_ripple_dict = fastRippleDetector.fit(data).chs_hfos_
ripple_and_fast_ripple_dict = find_coincident_HFOs(ripple_dict, fast_ripple_dict)

where

def find_coincident_HFOs(hfo_dict1, hfo_dict2):
  coincident_HFO_dict = {}
  for ch_name, hfo_struct1 in hfo_dict1.items():
    hfo_struct2 = hfo_dict2.get(ch_name)
    coincident_hfo_struct = _find_overlapping_HFOs(hfo_struct1, hfo_struct2)
    coincident_HFO_dict.update({ch_name: coincident_hfo_struct})

Use FunctionTransformer?

Using functionTransformer instead of making scikit-learn-like objects for hyperparameter optimization using GridSearchCV?

HFO Visualizations

Describe the problem

In a call with Dr. Jacobs, she mentioned that visualizing HFOs for clinicians is a huge hurdle. I can imagine it is for researchers too, where we need a robust and easy-to-use visualization engine for HFOs "detected" by a detector.

Describe your solution

Currently, MNE-Python is trying to add more robust visualization and even real-time visualizations, which may take awhile. However, when that is done, we can imagine bolstering that solution with:

  • real-time editing of montage
  • selecting channels
  • scrolling through time

Additional context

From meeting w/ Dr. Jacobs on 05/27/21

Make unit tests work for RMS/LineLength detectors

Some tests, code and algorithmic implementations were copied over from https://gitlab.com/icrc-bme/epycom/-/tree/master

For the test_detect.py::test_detect_hfo_rms and linelength, these tests should be made to work. I think there is a divergence possibly in the test, or somewhere in the algorithmic implementation here that causes the test to not work.

For context on the RMS detector:

Hilbert Detector Should Try to be RAM Aware

Describe the bug

Storing the Hilbert HFO statistic array is cumbersome especially if it's a gigantic dataset.

For example, a dataset from sickkids E6, has a huge array that can't be allocated.

Extracting parameters from /home/adam2392/hdd3/sickkids/sub-E6/ses-preresection/ieeg/sub-E6_ses-preresection_task-pre_acq-ecog_run-01_ieeg.vhdr...
Setting channel info structure...
Reading channel info from /home/adam2392/hdd3/sickkids/sub-E6/ses-preresection/ieeg/sub-E6_ses-preresection_task-pre_acq-ecog_run-01_channels.tsv.
Reading electrode coords from /home/adam2392/hdd3/sickkids/sub-E6/ses-preresection/ieeg/sub-E6_ses-preresection_acq-ecog_space-fs_electrodes.tsv.
<ipython-input-11-4873fb17ff74>:4: RuntimeWarning: Did not find any events.tsv associated with sub-E6_ses-preresection_task-pre_acq-ecog_run-01.

The search_str was "/home/adam2392/hdd3/sickkids/sub-E6/**/sub-E6_ses-preresection*events.tsv"
  raw = read_raw_bids(fpath, verbose=verbose)
<ipython-input-11-4873fb17ff74>:4: RuntimeWarning: Defaulting coordinate frame to unknown from coordinate system input Other
  raw = read_raw_bids(fpath, verbose=verbose)
<ipython-input-11-4873fb17ff74>:4: RuntimeWarning: There are channels without locations (n/a) that are not marked as bad: ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'C10', 'C11', 'C12', 'C13', 'C14', 'C16', 'C17', 'C18', 'C19', 'C20', 'C21', 'C22', 'C23', 'C24', 'C25', 'C26', 'C27', 'C28', 'C29', 'C30', 'C31', 'C32', 'C33', 'C34', 'C35', 'C36', 'C37', 'C38', 'C39', 'C40', 'C41', 'C42', 'C43', 'C44', 'C45', 'C46', 'C47', 'C48', 'C49', 'C50', 'C51', 'C52', 'C53', 'C54', 'C55', 'C56', 'C57', 'C58', 'C59', 'C60', 'C61', 'C62', 'C63', 'C64', '1D1', '1D2', '1D3', '1D4', '1D5', '1D6', '2D1', '2D2', '2D3', '2D4', '2D5', '2D6', '3D1', '3D2', '3D3', '3D4', '3D5', '3D6', 'C83', 'C84', 'C85', 'C86', 'C87', 'C88', 'C89', 'C90', 'C91', 'C92', 'C93', 'C94', 'C95', 'C96', 'C97', 'C98', 'C99', 'C100', 'C101', 'C102', 'C103', 'C104', 'C105', 'C106', 'C107', 'C108', 'C109', 'C110', 'C111', 'C112', 'C113', 'C114', 'C115', 'C116', 'C117', 'C118', 'C119', 'C120', 'C121', 'C122', 'C123']
  raw = read_raw_bids(fpath, verbose=verbose)
<ipython-input-11-4873fb17ff74>:4: RuntimeWarning: Fiducial point nasion not found, assuming identity unknown to head transformation
  raw = read_raw_bids(fpath, verbose=verbose)
---------------------------------------------------------------------------
MemoryError                               Traceback (most recent call last)
<ipython-input-11-4873fb17ff74> in <module>
     27 
     28             # run HFO detection
---> 29             detector.fit(raw)
     30 
     31             # extract the resulting annotations dataframe

~/Documents/mne-hfo/mne_hfo/base.py in fit(self, X, y)
    350 
    351         chs_hfos = {}
--> 352         self.hfo_event_arr_ = self._create_empty_event_arr()
    353         if self.n_jobs == 1:
    354             for idx in tqdm(range(self.n_chs)):

~/Documents/mne-hfo/mne_hfo/detect.py in _create_empty_event_arr(self)
    108         n_windows = self.n_times
    109         n_bands = len(self.freq_cutoffs) - 1
--> 110         hfo_event_arr = np.empty((self.n_chs, n_bands, n_windows))
    111         return hfo_event_arr
    112 

MemoryError: Unable to allocate 121. GiB for an array with shape (72, 61, 3704064) and data type float64

Expected results

What HilbertDetector should be able to do is:

i) determine RAM on the computer
ii) determine window chunks to apply (maybe we hard-code it to 1 GB a most?), based on number of channels and number of frequency bins -> get number of windows that amount to 1GB for the array
iii) run a loop over those windows to compute HFO events and store into temporary disc
iv) combine them into HFO annotatinos dataframe (note the edge of each window chunk needs to be smartly handled)

Including a generic "_find_baseline" method

Describe the problem

For more extravagant detectors, such as the Morphology detector, there is the notion of "comparing" against a baseline.

We should rigorously define in software abstractions what "baseline detections" generally look like, and implement.

How do we want to store HFO events?

In each Detector that is defined, we will want to define a series of sklearn-ish functions. Once we get one of these working fully, the others should be fairly straightforward. This discussion is to converge on the design of how the HFO events are stored in RAM.

For each detector, fit() is called, which will run a detection algorithm using the hyperparameters set. Series of arrays can be stored. For example, we can store chs_hfo_dict_, which is a dictionary of lists of endpoints of each HFO detected per channel (note the "_" character in line with sklearn).

Afterwards, to play nice with BIDS, visualization, mnepython, we want a way to store the actual events. In BIDS events, this essentially looks like a DataFrame.

onset | duration | sample | trial_type
------------------------------------

This can then be directly saved into .tsv and directly loaded via pd.read_csv(delimiter='\t', ...). Then we want functions that take this dataFrame and do useful things with it. For example:

def get_ch_metric(events_df, ch_name=None, metric='rate'):
# get channel(s) metrics from the dataframe structure

Initial Sprint

So to start, I propose we start small to make sure things look correct and API is not messy. We want a create_events_df function which creates the events dataframe from a variety of data structures:

def create_events_df(input: Dict | mne.io.BaseRaw):
# create events dataframe, either from a dcitionary of channel HFO endpoints, or from a mne-python Raw object with Annotations set. The annotations descriptions should have the term "hfo_<ch_name>", which is parsed out to create HFO events

Post processing detector(s)

Describe the problem

For list of detectors, looking at things like the kappa score, mutual info, etc.

Describe your solution

Pass in list of fit detectors, and reference dataset, and output a dictionary of pairwise comparisons.

Other improvements

Detecting dc shift windows, detecting spikes to annotate them.

Compute HFO rate from dataframe

Describe the problem

Please provide a clear and concise description of the problem.

Describe your solution

A clear and concise description of what you want to happen.

Describe possible alternatives

A clear and concise description of any alternative solutions or features you have considered.

Additional context

Add any other context or screenshots about the feature request here.

Add additional contributing documentation on how the directory structure is laid out

The general directory structure is:

  • docs/ or doc/ hosting documentation that gets rendered into a webpage, which then you can see online
  • mne-hfo/ is the directory with all the actual code
  • .circleci/ hosts testing code setup to run things on CircleCI (a cloud service company for testing code continuously)
  • .github/ hosts Github related code like templates for GH issues and also testing code setup for GH's cloud service for testing code
  • tests/ is the directory with all the unit and integration tests that test various pieces of the mne-hfo code.

CSDetector Implementation

Describe the problem

Please provide a clear and concise description of the problem.

Describe your solution

Import over CSDetector: ftp://msel.mayo.edu/cs_hfo_detector/Matlab/ into MNE-HFO implementation.

Describe possible alternatives

Many hyperparams, so might be difficult to implement 100% matching.

Additional context

Add any other context or screenshots about the feature request here.

Implement hilbert detectors

In addition to one in dev in detect.py, implement this one: Crépon B, et al. “Mapping interictal oscillations greater than 200 Hz recorded with intracranial macroelectrodes in human epilepsy

Add conda-forge package?

Hello, I just wanted to ask if you'd be interested in a conda-forge package of MNE-HFO? I could build one.

Asking because I'm right now looking at packages that would be nice to include in the MNE installers.

Will the latest release work with MNE-Python 0.24 and 1.0?

Compute HFO co-occurrences from two annotation dataframes

Describe the problem

Please provide a clear and concise description of the problem.

Describe your solution

A clear and concise description of what you want to happen.

Describe possible alternatives

A clear and concise description of any alternative solutions or features you have considered.

Additional context

Related: #9

Hilbert detector error message

I'm having a problem with the Hilbert detector. If raw is my mne-raw structure and fs is my sampling frequency,

detector=HilbertDetector(sfreq=fs, l_freq=80,h_freq=fs/2)
detector.fit(raw)
hfo_event_df = detector.hfo_event_df

produces the error message

Traceback (most recent call last):
  File "/Users/fishbacp/Desktop/Python_May_2021/Idle_script.py", line 20, in <module>
    detector.fit(raw)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/mne_hfo/detect.py", line 207, in fit
    n_chs, n_times = X.shape
AttributeError: 'RawEDF' object has no attribute 'shape'

Documentation sprint to properly host documentation

  1. gh-pages branch should be setup to host documentation.
  2. CircleCI should be setup to deploy the docs to to the dev/ folder on gh-pages branch.
  3. Then readthedocs should be setup for a free documentation hosting site.
  4. Setup examples/ or tutorials/ folder in the master branch to showcase how to run the detectors on mne-python data structures. Extra bonus to show it running with GridSearchCV function on a small dataset.
  5. Should follow mne-bids wiki: https://github.com/mne-tools/mne-bids/wiki/Explanation-of-Documentation-Setup to setup the documentation and make a stable release when the initial detectors have been validated.

Discover if Hilbert Detector's sparse detections is by design or flaw

Describe the bug

The Hilbert Detector picks up way less HFO events than either of the RMS or LineLength Detectors. We need to figure out if this is by design or if it is a code bug before we use this in a study.

Steps to reproduce

Run the unit tests, pytest ./tests/test_detect.py::test_detect_hfo_hilbert and see that it misses detections

Expected results

We do not currently know what the expected output should be, so this might be right

Actual results

The first event is missed.

Refactor Hilbert to be More Memory Efficient

Describe the problem

The current implementation has an internal state that stores a (n_channels x n_times x n_bands) matrix, which ends up eating up 30+GB of RAM if using linear band method and 20+ GB of RAM if using the log band method for a 30 minute clip sampled at 2000 Hz.

Describe your solution

Default to processing each channels data sequentially, parallelizing over bands, and merging in post-processing when array sizes are more manageable.

Describe possible alternatives

The alternative is to keep it as it is since it runs, just inefficiently

Demonstration of using GridSearchCV and RandomSearchCV

Describe the problem

Demonstrate how using the scikit-learn's api to find optimal combination of hyperparameters for a Detector.
Since optimal combination depends on the Detector having a ground-truth to compare to, then this must be
run on the Fedele dataset.

This should be summarized inside the tutorial notebook.

Describe your solution

This would be a two-step PR:

Refactoring match_detections to be simple API and return y_true and y_pred as lists

  1. Refactor match_detections
    Match detections does not need bn kwarg. Seems unnecessary considering we are assuming a structured DataFrame.
    It also does not need freq_name. Instead it should look like this:
def match_detections(ytrue_df, ypredict_df, label:str=None, sec_margin: float=1.):

where label if passed in will filter which label to match (e.g. fastripple, or ripple, or just plain old hfo). One
should be able to infer the sampling rate based on the DataFrame. In addition, rn the function is unusable and hard to read, so if you want to keep it, keep it, but make sure you can describe how it's working. FYI, I copied it over from epycom, so no guarantee is functions in any way (no tests).

  1. Add unit test for match_detections

See existing unit tests and how it's done.

Implement Detector.predict() to make *SearchCV functions work and demonstrate

Next, make the scikit-learn's *SearchCV functions work, which will require implementing predict and score.
I believe predict is already done. It just needs to "detect the HFOs". score needs to be implemented. Score
should essentially call match_detections and then compute a metric using scoring_func. It should allow
kwargs that need to be passed to match_detections (e.g. label, or sec_margin to customize how we want to score).

  1. implement score function
  2. demonstrate now in sequence using the GridSearchCV and RandomSearchCV functions on one subject of the Fedele dataset.

Refactor to use mne's apply_function

Describe the problem

Standardizing the detector functions is very important, so we need a good way of ensuring that a standard is held. This is a down the line nice-to-have.

Describe your solution

We could refactor each of our detectors to work with mne's apply_function
https://mne.tools/stable/generated/mne.io.Raw.html#mne.io.Raw.apply_function
which should do some of the channel processing for us.

Describe possible alternatives

The current method works since we are at least constrained within the sklearn standard. This refactor would be in addition to that.

Compare Detections

Describe the problem

There needs to be an easy way to compare two different detectors.

Describe your solution

A module that takes two Detectors and outputs agreement per channel.

Describe possible alternatives

We could also do a similar module that takes the output detection dataframes directly.

No module named 'mne.externals'

Hi, I found when I used the dev version of mne, this error would come out.

Traceback (most recent call last):
    from mne_hfo import LineLengthDetector, RMSDetector, events_to_annotations, compute_chs_hfo_rates
  File "D:\ProgramData\Miniconda3\lib\site-packages\mne_hfo\__init__.py", line 5, in <module>
    from mne_hfo.detect import RMSDetector, LineLengthDetector, HilbertDetector
  File "D:\ProgramData\Miniconda3\lib\site-packages\mne_hfo\detect.py", line 6, in <module>
    from mne_hfo.base import Detector
  File "D:\ProgramData\Miniconda3\lib\site-packages\mne_hfo\base.py", line 13, in <module>
    from mne_hfo.io import create_events_df, events_to_annotations
  File "D:\ProgramData\Miniconda3\lib\site-packages\mne_hfo\io.py", line 12, in <module>
    from mne_bids import read_raw_bids, get_entities_from_fname, BIDSPath
  File "D:\ProgramData\Miniconda3\lib\site-packages\mne_bids\__init__.py", line 5, in <module>
    from mne_bids.report import make_report
  File "D:\ProgramData\Miniconda3\lib\site-packages\mne_bids\report.py", line 11, in <module>
    from mne.externals.tempita import Template
ModuleNotFoundError: No module named 'mne.externals'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.