-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Plot ica comparison #13215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Plot ica comparison #13215
Conversation
Added visualization plots for different ICA algorithms for clean and noisy data
added visualization plots for different ICA algorithms and added fit times for both clean and noisy data.
for more information, see https://pre-commit.ci
created 13215.enhancement.rst file
…if/mne-python into plot_ica_comparison
Hi @drammock |
Maybe one for @cbrnr to review? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if the proposed changes add enough value to the existing example. Currently, only a few ad-hoc comparisons (i.e., adding a fixed amount of Gaussian noise and visually comparing a single ocular IC) are made, but it might be necessary to use a more systematic approach in order to make more substantial comparisons.
For example, if we want to explore the influence of noise, a more systematic approach would be to (1) compare different levels of SNR (e.g., 20dB, 0dB, -20dB) and (2) use different kinds of noise (not only Gaussian, but also 1/f, 50Hz/60Hz line noise, EMG-like bursts of noise, ...), similar as in this study.
I'm also not sure that showing an ocular component for each algorithm is a useful comparison. In my experience, most ICA algorithms will easily detect and separate such activity, so I would not expect any major differences. Instead, separating brain components might be the more interesting analysis where algorithms might differ. For example, this study has shown that ICAs based on mutual information outperform other algorithms in terms of dipolarity, which usually indicate cortical sources.
Having said that, I don't think that we should extend this little example to a full-blown scientifically rigorous study.
This example is for educational purposes. | ||
""" | ||
# Authors: Pierre Ablin <[email protected]> | ||
# Ganasekhar Kalla <[email protected]> |
There was a problem hiding this comment.
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.
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()) |
There was a problem hiding this comment.
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 = mne.io.read_raw_fif(raw_file, preload=True) | ||
raw.pick_types(meg=True, eeg=False, eog=True) | ||
raw.crop(0, 60) |
There was a problem hiding this comment.
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.
|
||
reject = dict(mag=5e-12, grad=4000e-13) | ||
raw.filter(1, 30, fir_design="firwin") | ||
# Copy for clean and noisy |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
# Copy for clean and noisy | ||
raw_clean = raw.copy() | ||
raw_noisy = raw_clean.copy() | ||
noise = 1e-12 * np.random.randn(*raw_noisy._data.shape) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
||
# Rejection thresholds | ||
reject_clean = dict(mag=5e-12, grad=4000e-13) | ||
reject_noisy = dict(mag=1e-11, grad=8000e-13) |
There was a problem hiding this comment.
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?
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Updated code with broken long line |
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
EOG Component Detection
Reference Issue:
Addresses #9002
What does this implement/fix?
This draft PR improves the ICA comparison example by:
Comparing multiple ICA algorithms: FastICA, Picard, Infomax, and Extended Infomax.
Evaluating performance on both clean and synthetically noisy MEG data.
Detecting and visualizing EOG-related components for each method.
Showing side-by-side component comparisons to enhance interpretability.
Displaying fit time per method for performance benchmarking.
Providing a more informative educational example (moving beyond just a "how-to").
Preview: Output Snapshots
Fastica method on clean data: https://github.com/user-attachments/assets/03fbe1d3-d8ad-4349-96fa-f6743e42ff88
Fastica method on noisy data: https://github.com/user-attachments/assets/59c86307-a9fe-4f2b-93d6-434f2131181e
EOG component comparison: https://github.com/user-attachments/assets/a670261b-a32a-4da0-a25c-d8332e3924db
Additional Information:
This is a work-in-progress PR as encouraged by the maintainers. I'm looking for early feedback on:
Visual clarity of EOG component comparisons.
Whether to include more deterministic checks (e.g., random_state).
In addition, I have a couple of questions:
EOG Component Comparison:
The issue refers to comparing EOG components. Currently, I've selected the most likely EOG-related component based on each ICA method's output. Could you clarify what “the same” EOG component refers to in this context? Are you expecting comparisons based on a specific pre-determined EOG component or should this focus on the component detected by each algorithm?
Summary of Changes:
Slightly reworded the description for clarity.
Structured the PR content with bullet points to improve readability.
Reworded the EOG component question to avoid confusion.