Skip to content

Conversation

Ganasekhar-gif
Copy link

@Ganasekhar-gif Ganasekhar-gif commented Apr 15, 2025

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.

Ganasekhar-gif and others added 7 commits April 15, 2025 14:36
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.
created 13215.enhancement.rst file
@Ganasekhar-gif Ganasekhar-gif marked this pull request as ready for review April 21, 2025 11:16
@Ganasekhar-gif
Copy link
Author

Hi @drammock
it's been a while but i still wanted to complete this issue so can you please review this PR and give me the feedback

@larsoner
Copy link
Member

larsoner commented Sep 2, 2025

Maybe one for @cbrnr to review?

@cbrnr cbrnr self-assigned this Sep 8, 2025
Copy link
Contributor

@cbrnr cbrnr left a 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]>
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.

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.

Comment on lines 48 to 50
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.


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.

Comment on lines 55 to 56
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.

# Copy for clean and noisy
raw_clean = raw.copy()
raw_noisy = raw_clean.copy()
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.


# 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?

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants