Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9b76bc0
update ica_comparison.py
Ganasekhar-gif Apr 15, 2025
dc72b25
create newfeature.rst
Ganasekhar-gif Apr 15, 2025
f4b0bd8
update ica_comparison.py
Ganasekhar-gif Apr 15, 2025
feaa0b6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 15, 2025
ad7b1a1
create 13215.enhancement.rst
Ganasekhar-gif Apr 15, 2025
a513556
Merge branch 'plot_ica_comparison' of https://github.com/Ganasekhar-g…
Ganasekhar-gif Apr 15, 2025
d94f2a5
Merge branch 'main' into plot_ica_comparison
Ganasekhar-gif Apr 21, 2025
bc55115
Merge branch 'main' into plot_ica_comparison
Ganasekhar-gif May 2, 2025
dae4983
Merge branch 'main' into plot_ica_comparison
Ganasekhar-gif May 7, 2025
422710c
Merge branch 'main' into plot_ica_comparison
Ganasekhar-gif Jun 3, 2025
f029801
Merge branch 'main' into plot_ica_comparison
cbrnr Sep 8, 2025
4ca2431
Fix changelog entry
cbrnr Sep 8, 2025
71ec45e
Fix authors
cbrnr Sep 8, 2025
b1920f8
Rename changelog
cbrnr Sep 8, 2025
3e2dc76
added different noises and different snr levels in ica_comparison.py
Ganasekhar-gif Sep 11, 2025
ad522e8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2025
77f1f3a
Add ICA algorithm comparison example with noise robustness evaluation
Ganasekhar-gif Sep 11, 2025
55c8f13
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2025
9214d09
DOC: Add ICA algorithm comparison example with noise robustness evalu…
Ganasekhar-gif Sep 11, 2025
149ed7f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2025
47ab1e6
Update ica_comparison.py
Ganasekhar-gif Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changes/dev/13215.other.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Extend :ref:`ex-ica-comp` example on comparing ICA algorithms with clean vs noisy MEG data, by :newcontrib:`Ganasekhar Kalla`.
1 change: 1 addition & 0 deletions doc/changes/names.inc
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
.. _Florin Pop: https://github.com/florin-pop
.. _Frederik Weber: https://github.com/Frederik-D-Weber
.. _Fu-Te Wong: https://github.com/zuxfoucault
.. _Ganasekhar Kalla: https://github.com/Ganasekhar-gif
.. _Gennadiy Belonosov: https://github.com/Genuster
.. _Geoff Brookshire: https://github.com/gbrookshire
.. _George O'Neill: https://georgeoneill.github.io
Expand Down
126 changes: 103 additions & 23 deletions examples/preprocessing/ica_comparison.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,72 @@
"""
.. _ex-ica-comp:

===========================================
Compare the different ICA algorithms in MNE
===========================================
===========================================================
Compare the performance of different ICA algorithms in MNE
===========================================================

Different ICA algorithms are fit to raw MEG data, and the corresponding maps
are displayed.
This example compares various ICA algorithms (FastICA, Picard, Infomax,
Extended Infomax) on the same raw MEG data. For each algorithm:

- The ICA fit time (speed) is shown
- All components (up to 20) are visualized
- The EOG-related component from each method is detected and compared
- Comparison on clean vs noisy data is done

Note: In typical preprocessing, only one ICA algorithm is used.
This example is for educational purposes.
"""
# Authors: Pierre Ablin <[email protected]>
# Ganasekhar Kalla <[email protected]>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not remove previous authors. I've already fixed that for you.

#
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.

# %%

from pathlib import Path
from time import time

import numpy as np

import mne
from mne.datasets import sample
from mne.preprocessing import ICA

print(__doc__)

# %%

# Read and preprocess the data. Preprocessing consists of:
#
# - MEG channel selection
# - 1-30 Hz band-pass filter

data_path = sample.data_path()
meg_path = data_path / "MEG" / "sample"
raw_fname = meg_path / "sample_audvis_filt-0-40_raw.fif"
# Load sample dataset
data_path = Path(sample.data_path())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sample.data_path() is already a pathlib.Path object, so no need to convert.

raw_file = data_path / "MEG" / "sample" / "sample_audvis_raw.fif"
raw = mne.io.read_raw_fif(raw_file, preload=True)
raw.pick_types(meg=True, eeg=False, eog=True)
raw.crop(0, 60)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pick_types method is deprecated in favor of pick. Please just use the previous code, it was perfectly fine.


raw = mne.io.read_raw_fif(raw_fname).crop(0, 60).pick("meg").load_data()
# %%

reject = dict(mag=5e-12, grad=4000e-13)
raw.filter(1, 30, fir_design="firwin")
# Copy for clean and noisy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove the filter step? It is essential for good ICA performance to remove low frequency drifts, so this change likely negatively affects the decomposition.

raw_clean = raw.copy()
raw_noisy = raw_clean.copy()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates three raw objects (original, clean, and noisy), but you only need two, because the original is equal to the clean data.

noise = 1e-12 * np.random.randn(*raw_noisy._data.shape)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding Gaussian noise is just one type of possible noise sources. In addition, I'd prefer to see the performance of ICA algorithms with different levels of SNR instead of using a fixed noise amplitude.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @cbrnr
Thank you for the suggestion! I’ve updated the example to address this: in addition to Gaussian noise, I’ve now included multiple noise types (Gaussian, pink, line, and EMG). For each noise type, ICA performance is evaluated at different SNR levels (e.g., 10 dB and 0 dB), rather than with a fixed amplitude. This way, the example shows how the algorithms behave under a broader range of noise conditions and robustness settings.

raw_noisy._data += noise

# Rejection thresholds
reject_clean = dict(mag=5e-12, grad=4000e-13)
reject_noisy = dict(mag=1e-11, grad=8000e-13)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use different rejection thresholds?


# %%
# Define a function that runs ICA on the raw MEG data and plots the components


def run_ica(method, fit_params=None):
# Run ICA
def run_ica(raw_input, method, fit_params=None, reject=None):
print(f"\nRunning ICA with: {method}")
ica = ICA(
n_components=20,
method=method,
Expand All @@ -53,24 +75,82 @@ def run_ica(method, fit_params=None):
random_state=0,
)
t0 = time()
ica.fit(raw, reject=reject)
ica.fit(raw_input, reject=reject)
fit_time = time() - t0
title = f"ICA decomposition using {method} (took {fit_time:.1f}s)"
print(f"Fitting ICA took {fit_time:.1f}s.")

# Updated code with broken long line
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Updated code with broken long line

title = (
f"ICA decomposition using {method} on "
f"{'noisy' if raw_input is raw_noisy else 'clean'} data\n"
f"(took {fit_time:.1f}s)"
)
ica.plot_components(title=title)

return ica, fit_time


# %%
# FastICA
run_ica("fastica")


# Run all ICA methods
def run_all_ica(raw_input, label, reject):
icas = {}
fit_times = {}
eog_components = {}
for method, params in [
("fastica", None),
("picard", None),
("infomax", None),
("infomax", {"extended": True}),
]:
name = f"{method}" if not params else f"{method}_extended"
full_label = f"{label}_{name}"
ica, t = run_ica(raw_input, method, params, reject)
icas[full_label] = ica
fit_times[full_label] = t

eog_inds, _ = ica.find_bads_eog(raw_input, threshold=3.0, verbose="ERROR")
if eog_inds:
eog_components[full_label] = eog_inds[0]
print(f"{full_label}:Detected EOG comp at index {eog_inds[0]}")
else:
eog_components[full_label] = None
print(f"{full_label}: No EOG component detected")

return icas, fit_times, eog_components


# %%
# Picard
run_ica("picard")


# Run on both raw versions
icas_clean, times_clean, eog_clean = run_all_ica(raw_clean, "clean", reject_clean)
icas_noisy, times_noisy, eog_noisy = run_all_ica(raw_noisy, "noisy", reject_noisy)

# Combine results
icas = {**icas_clean, **icas_noisy}
times = {**times_clean, **times_noisy}
eog_comps = {**eog_clean, **eog_noisy}

# %%
# Infomax
run_ica("infomax")

# Clean EOG components for each algorithm (Column 1)
for method in ["fastica", "picard", "infomax", "infomax_extended"]:
key = f"clean_{method}"
comp = eog_comps.get(key)
if comp is not None:
icas[key].plot_components(
picks=[comp], title=f"{key} - EOG Component (Clean Data)", show=True
)

# %%
# Extended Infomax
run_ica("infomax", fit_params=dict(extended=True))

# Noisy EOG components for each algorithm (Column 2)
for method in ["fastica", "picard", "infomax", "infomax_extended"]:
key = f"noisy_{method}"
comp = eog_comps.get(key)
if comp is not None:
icas[key].plot_components(
picks=[comp], title=f"{key} - EOG Component (Noisy Data)", show=True
)
Loading