From 9b76bc092d34cddd02cc179bb4da4b75f937faa9 Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Tue, 15 Apr 2025 14:36:01 +0530 Subject: [PATCH 01/15] update ica_comparison.py Added visualization plots for different ICA algorithms for clean and noisy data --- examples/preprocessing/ica_comparison.py | 141 ++++++++++++++++++----- 1 file changed, 114 insertions(+), 27 deletions(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index d4246b80362..cef36fc6d8d 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -1,50 +1,70 @@ """ .. _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 + +# authors : Ganasekhar Kalla # # License: BSD-3-Clause # Copyright the MNE-Python contributors. # %% -from time import time - import mne -from mne.datasets import sample from mne.preprocessing import ICA +from mne.datasets import sample +import numpy as np +from time import time +from pathlib import Path 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()) +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) -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 +raw_clean = raw.copy() +raw_noisy = raw_clean.copy() +noise = 1e-12 * np.random.randn(*raw_noisy._data.shape) +raw_noisy._data += noise +# Rejection thresholds +reject_clean = dict(mag=5e-12, grad=4000e-13) +reject_noisy = dict(mag=1e-11, grad=8000e-13) # %% -# 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, @@ -53,24 +73,91 @@ 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 + 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 + ) From dc72b25abe4d2957ad0f0bebe2f6c8bcba0228b2 Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Tue, 15 Apr 2025 15:15:15 +0530 Subject: [PATCH 02/15] create newfeature.rst --- doc/changes/devel/0000.newfeature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/devel/0000.newfeature.rst diff --git a/doc/changes/devel/0000.newfeature.rst b/doc/changes/devel/0000.newfeature.rst new file mode 100644 index 00000000000..7b9675e8dd1 --- /dev/null +++ b/doc/changes/devel/0000.newfeature.rst @@ -0,0 +1 @@ +Add new example :ref:`ex-ica-comp` that compares performance of multiple ICA algorithms (FastICA, Picard, Infomax, Extended Infomax) on clean vs noisy MEG data showing differences in runtime, component detection and EOG-related ICs, by `Ganasekhar Kalla`_. From f4b0bd8bd3fb270d540283db8bd8bb0b045f41f3 Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Tue, 15 Apr 2025 20:49:51 +0530 Subject: [PATCH 03/15] update ica_comparison.py added visualization plots for different ICA algorithms and added fit times for both clean and noisy data. --- doc/changes/devel/0000.enhancement.rst | 1 + doc/changes/names.inc | 1 + examples/preprocessing/ica_comparison.py | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 doc/changes/devel/0000.enhancement.rst diff --git a/doc/changes/devel/0000.enhancement.rst b/doc/changes/devel/0000.enhancement.rst new file mode 100644 index 00000000000..ff668050076 --- /dev/null +++ b/doc/changes/devel/0000.enhancement.rst @@ -0,0 +1 @@ +Add example :ref:`ex-ica-comp` comparing ICA algorithms on clean vs noisy MEG data, by :newcontrib:`Ganasekhar Kalla`. diff --git a/doc/changes/names.inc b/doc/changes/names.inc index 0d5ee6a5c73..c4ee9923948 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -96,6 +96,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 diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index cef36fc6d8d..90d212c8231 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -95,8 +95,6 @@ def run_all_ica(raw_input, label, reject): icas = {} fit_times = {} eog_components = {} - - for method, params in [ ("fastica", None), ("picard", None), @@ -123,6 +121,7 @@ def run_all_ica(raw_input, label, reject): # %% + # Run on both raw versions icas_clean, times_clean, eog_clean = run_all_ica( raw_clean, "clean", reject_clean From feaa0b6bd3401b6c8909cf8e8d56ac3917496b09 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:22:29 +0000 Subject: [PATCH 04/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/preprocessing/ica_comparison.py | 34 ++++++++++-------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index 90d212c8231..3cf083573ef 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -24,12 +24,14 @@ # %% +from pathlib import Path +from time import time + +import numpy as np + import mne -from mne.preprocessing import ICA from mne.datasets import sample -import numpy as np -from time import time -from pathlib import Path +from mne.preprocessing import ICA print(__doc__) @@ -42,7 +44,7 @@ # Load sample dataset data_path = Path(sample.data_path()) -raw_file = data_path / 'MEG' / 'sample' / 'sample_audvis_raw.fif' +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) @@ -87,6 +89,7 @@ def run_ica(raw_input, method, fit_params=None, reject=None): return ica, fit_time + # %% @@ -107,9 +110,7 @@ def run_all_ica(raw_input, label, reject): icas[full_label] = ica fit_times[full_label] = t - eog_inds, _ = ica.find_bads_eog( - raw_input, threshold=3.0, verbose='ERROR' - ) + 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]}") @@ -119,16 +120,13 @@ def run_all_ica(raw_input, label, reject): return icas, fit_times, eog_components + # %% # 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 -) +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} @@ -143,9 +141,7 @@ def run_all_ica(raw_input, label, reject): 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 + picks=[comp], title=f"{key} - EOG Component (Clean Data)", show=True ) # %% @@ -156,7 +152,5 @@ def run_all_ica(raw_input, label, reject): 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 + picks=[comp], title=f"{key} - EOG Component (Noisy Data)", show=True ) From ad7b1a16d6172e9ff8235a138c69134c574b790c Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Tue, 15 Apr 2025 20:53:29 +0530 Subject: [PATCH 05/15] create 13215.enhancement.rst created 13215.enhancement.rst file --- doc/changes/devel/13215.enhancement.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/devel/13215.enhancement.rst diff --git a/doc/changes/devel/13215.enhancement.rst b/doc/changes/devel/13215.enhancement.rst new file mode 100644 index 00000000000..ff668050076 --- /dev/null +++ b/doc/changes/devel/13215.enhancement.rst @@ -0,0 +1 @@ +Add example :ref:`ex-ica-comp` comparing ICA algorithms on clean vs noisy MEG data, by :newcontrib:`Ganasekhar Kalla`. From 4ca2431914e0466d260a50e8338800598349d7db Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 8 Sep 2025 08:30:07 +0200 Subject: [PATCH 06/15] Fix changelog entry --- doc/changes/dev/13215.enhancement.rst | 1 + doc/changes/devel/0000.enhancement.rst | 1 - doc/changes/devel/0000.newfeature.rst | 1 - doc/changes/devel/13215.enhancement.rst | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) create mode 100644 doc/changes/dev/13215.enhancement.rst delete mode 100644 doc/changes/devel/0000.enhancement.rst delete mode 100644 doc/changes/devel/0000.newfeature.rst delete mode 100644 doc/changes/devel/13215.enhancement.rst diff --git a/doc/changes/dev/13215.enhancement.rst b/doc/changes/dev/13215.enhancement.rst new file mode 100644 index 00000000000..6db5d9a7fb9 --- /dev/null +++ b/doc/changes/dev/13215.enhancement.rst @@ -0,0 +1 @@ +Extend :ref:`ex-ica-comp` example on comparing ICA algorithms with clean vs noisy MEG data, by :newcontrib:`Ganasekhar Kalla`. diff --git a/doc/changes/devel/0000.enhancement.rst b/doc/changes/devel/0000.enhancement.rst deleted file mode 100644 index ff668050076..00000000000 --- a/doc/changes/devel/0000.enhancement.rst +++ /dev/null @@ -1 +0,0 @@ -Add example :ref:`ex-ica-comp` comparing ICA algorithms on clean vs noisy MEG data, by :newcontrib:`Ganasekhar Kalla`. diff --git a/doc/changes/devel/0000.newfeature.rst b/doc/changes/devel/0000.newfeature.rst deleted file mode 100644 index 7b9675e8dd1..00000000000 --- a/doc/changes/devel/0000.newfeature.rst +++ /dev/null @@ -1 +0,0 @@ -Add new example :ref:`ex-ica-comp` that compares performance of multiple ICA algorithms (FastICA, Picard, Infomax, Extended Infomax) on clean vs noisy MEG data showing differences in runtime, component detection and EOG-related ICs, by `Ganasekhar Kalla`_. diff --git a/doc/changes/devel/13215.enhancement.rst b/doc/changes/devel/13215.enhancement.rst deleted file mode 100644 index ff668050076..00000000000 --- a/doc/changes/devel/13215.enhancement.rst +++ /dev/null @@ -1 +0,0 @@ -Add example :ref:`ex-ica-comp` comparing ICA algorithms on clean vs noisy MEG data, by :newcontrib:`Ganasekhar Kalla`. From 71ec45e87307850d0336c8c7caf928975b23b536 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 8 Sep 2025 08:34:14 +0200 Subject: [PATCH 07/15] Fix authors --- examples/preprocessing/ica_comparison.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index 3cf083573ef..fe16e527d3e 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -16,8 +16,8 @@ Note: In typical preprocessing, only one ICA algorithm is used. This example is for educational purposes. """ - -# authors : Ganasekhar Kalla +# Authors: Pierre Ablin +# Ganasekhar Kalla # # License: BSD-3-Clause # Copyright the MNE-Python contributors. From b1920f841295598e74c1d0cdad15bde160ca7358 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 8 Sep 2025 08:38:42 +0200 Subject: [PATCH 08/15] Rename changelog --- doc/changes/dev/{13215.enhancement.rst => 13215.other.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changes/dev/{13215.enhancement.rst => 13215.other.rst} (100%) diff --git a/doc/changes/dev/13215.enhancement.rst b/doc/changes/dev/13215.other.rst similarity index 100% rename from doc/changes/dev/13215.enhancement.rst rename to doc/changes/dev/13215.other.rst From 3e2dc760d9ff206b949c3d0f22e77d0bdd1df850 Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Thu, 11 Sep 2025 18:04:27 +0530 Subject: [PATCH 09/15] added different noises and different snr levels in ica_comparison.py --- examples/preprocessing/ica_comparison.py | 227 +++++++++++++++++++---- 1 file changed, 187 insertions(+), 40 deletions(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index fe16e527d3e..6c2888ea95b 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -16,6 +16,7 @@ Note: In typical preprocessing, only one ICA algorithm is used. This example is for educational purposes. """ + # Authors: Pierre Ablin # Ganasekhar Kalla # @@ -24,17 +25,22 @@ # %% -from pathlib import Path from time import time +import warnings import numpy as np import mne from mne.datasets import sample from mne.preprocessing import ICA +from sklearn.exceptions import ConvergenceWarning print(__doc__) +# Reduce console noise from MNE and sklearn +mne.set_log_level("ERROR") +warnings.filterwarnings("ignore", category=ConvergenceWarning) + # %% # Read and preprocess the data. Preprocessing consists of: @@ -43,30 +49,126 @@ # - 1-30 Hz band-pass filter # Load sample dataset -data_path = Path(sample.data_path()) +data_path = sample.data_path() 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) +raw = mne.io.read_raw_fif(raw_file).crop(0, 60).pick(["meg", "eog"]).load_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) -raw_noisy._data += noise - -# Rejection thresholds +# Copy for clean +raw_clean = raw + + +def _scale_to_rms(noise, target_rms): + curr_rms = np.sqrt(np.mean(noise**2, axis=1, keepdims=True)) + 1e-30 + return noise * (target_rms / curr_rms) + + +# Noise generators +def _gaussian_noise(shape, rng): + return rng.randn(*shape) + + +def _pink_noise(shape, rng, sfreq): + n_channels, n_times = shape + # Build frequency weights ~ 1/sqrt(f) to get 1/f power spectrum + freqs = np.fft.rfftfreq(n_times, d=1.0 / sfreq) + weights = np.ones_like(freqs) + nonzero = freqs > 0 + weights[nonzero] = 1.0 / np.sqrt(freqs[nonzero]) + noise = rng.randn(n_channels, n_times) + noise_fft = np.fft.rfft(noise, axis=1) + noise_fft *= weights[np.newaxis, :] + pink = np.fft.irfft(noise_fft, n=n_times, axis=1) + return pink + + +def _line_noise(shape, rng, sfreq, line_freq): + n_channels, n_times = shape + t = np.arange(n_times) / sfreq + nyq = sfreq / 2.0 + harmonics = [h for h in [1, 2, 3] if h * line_freq < nyq] + base = np.zeros((n_channels, n_times)) + for h in harmonics: + phase = rng.rand(n_channels, 1) * 2 * np.pi + amp = 1.0 / h + base += amp * np.sin(2 * np.pi * h * line_freq * t + phase) + return base + + +def _emg_bursts( + shape, rng, sfreq, low=20.0, high=100.0, burst_prob=0.01, burst_len_s=0.2 +): + n_channels, n_times = shape + # Start with band-limited noise in EMG band via FFT masking + white = rng.randn(n_channels, n_times) + freqs = np.fft.rfftfreq(n_times, d=1.0 / sfreq) + mask = (freqs >= low) & (freqs <= high) + white_fft = np.fft.rfft(white, axis=1) + white_fft[:, ~mask] = 0.0 + emg_band = np.fft.irfft(white_fft, n=n_times, axis=1) + # Create sparse burst envelopes + burst_len = max(1, int(burst_len_s * sfreq)) + envelope = np.zeros((n_channels, n_times)) + for ch in range(n_channels): + idx = 0 + while idx < n_times: + if rng.rand() < burst_prob: + end = min(n_times, idx + burst_len) + envelope[ch, idx:end] = 1.0 + idx = end + else: + idx += burst_len + return emg_band * envelope + + +# Helper: add noise to reach target SNR (in dB) with selectable type +def add_noise_for_snr( + raw_input, snr_db, random_state=0, noise_type="gaussian", line_freq=50 +): + rng = np.random.RandomState(random_state) + data = raw_input._data + sfreq = raw_input.info["sfreq"] + # Per-channel RMS so SNR is matched channel-wise + signal_rms = np.sqrt(np.mean(data**2, axis=1, keepdims=True)) + 1e-30 + amp_ratio = 10 ** (-snr_db / 20.0) + noise_rms = amp_ratio * signal_rms + + if noise_type == "gaussian": + noise = _gaussian_noise(data.shape, rng) + elif noise_type == "pink": + noise = _pink_noise(data.shape, rng, sfreq) + elif noise_type in ("line50", "line60"): + lf = 50 if noise_type == "line50" else 60 + noise = _line_noise(data.shape, rng, sfreq, lf) + elif noise_type == "emg": + noise = _emg_bursts(data.shape, rng, sfreq) + else: + raise ValueError(f"Unknown noise_type: {noise_type}") + + noise = _scale_to_rms(noise, noise_rms) + raw_noisy_local = raw_input.copy() + raw_noisy_local._data = data + noise + return raw_noisy_local, amp_ratio + + +# Baseline rejection thresholds for clean data reject_clean = dict(mag=5e-12, grad=4000e-13) -reject_noisy = dict(mag=1e-11, grad=8000e-13) + +# Choose SNR levels (in dB) +snr_levels = [10, 0] +# Choose noise types to evaluate: 'gaussian', 'pink', 'line50'/'line60', 'emg' +noise_types = ["gaussian", "pink", "line50", "emg"] # %% # Run ICA -def run_ica(raw_input, method, fit_params=None, reject=None): - print(f"\nRunning ICA with: {method}") +def run_ica( + raw_input, method, fit_params=None, reject=None, label=None, display_name=None +): + name_for_print = display_name if display_name is not None else method + print(f"\nRunning ICA with: {name_for_print}") ica = ICA( n_components=20, method=method, @@ -74,17 +176,31 @@ def run_ica(raw_input, method, fit_params=None, reject=None): max_iter="auto", random_state=0, ) + # Emit informational lines similar to MNE's verbose output + n_channels = raw_input.info["nchan"] + print( + f"Fitting ICA to data using {n_channels} channels (please be patient, this may take a while)" + ) + print("Selecting by number: 20 components") t0 = time() - ica.fit(raw_input, reject=reject) + # Suppress verbose logs during fitting + with mne.use_log_level("ERROR"): + try: + ica.fit(raw_input, reject=reject, verbose="ERROR") + except RuntimeError as err: + msg = str(err) + if "No clean segment found" in msg: + print( + "No clean segment with current reject; retrying without rejection …" + ) + ica.fit(raw_input, reject=None, verbose="ERROR") + else: + raise fit_time = time() - t0 print(f"Fitting ICA took {fit_time:.1f}s.") - # 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)" - ) + data_label = label if label is not None else "data" + title = f"ICA decomposition using {name_for_print} on {data_label}\n(took {fit_time:.1f}s)" ica.plot_components(title=title) return ica, fit_time @@ -104,9 +220,14 @@ def run_all_ica(raw_input, label, reject): ("infomax", None), ("infomax", {"extended": True}), ]: - name = f"{method}" if not params else f"{method}_extended" + # Clarify label and display name for extended infomax + is_extended = method == "infomax" and params and params.get("extended", False) + name = "infomax_extended" if is_extended else method + display_name = "infomax (extended)" if is_extended else method full_label = f"{label}_{name}" - ica, t = run_ica(raw_input, method, params, reject) + ica, t = run_ica( + raw_input, method, params, reject, label=label, display_name=display_name + ) icas[full_label] = ica fit_times[full_label] = t @@ -124,33 +245,59 @@ def run_all_ica(raw_input, label, reject): # %% -# Run on both raw versions +# Build noisy datasets for each SNR level and noise type with associated reject thresholds +noisy_sets = {} +idx = 0 +for snr_db in snr_levels: + for ntype in noise_types: + raw_noisy_level, amp_ratio = add_noise_for_snr( + raw_clean, snr_db, random_state=idx, noise_type=ntype + ) + idx += 1 + # Scale reject thresholds based on noise amplitude ratio + reject_scaled = dict( + mag=reject_clean["mag"] * (1.0 + amp_ratio), + grad=reject_clean["grad"] * (1.0 + amp_ratio), + ) + label = f"noisy_{ntype}_snr{snr_db}dB" + noisy_sets[label] = (raw_noisy_level, reject_scaled) + +# Run on clean 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} +# Run on each noisy SNR level +icas_all = {**icas_clean} +times_all = {**times_clean} +eog_all = {**eog_clean} +for label, (raw_noisy_level, reject_scaled) in noisy_sets.items(): + icas_level, times_level, eog_level = run_all_ica( + raw_noisy_level, label, reject_scaled + ) + icas_all.update(icas_level) + times_all.update(times_level) + eog_all.update(eog_level) # %% # 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) + comp = eog_all.get(key) if comp is not None: - icas[key].plot_components( + icas_all[key].plot_components( picks=[comp], title=f"{key} - EOG Component (Clean Data)", show=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 - ) +# Noisy EOG components for each algorithm at each SNR level and noise type +for label in noisy_sets.keys(): + for method in ["fastica", "picard", "infomax", "infomax_extended"]: + key = f"{label}_{method}" + comp = eog_all.get(key) + if comp is not None: + icas_all[key].plot_components( + picks=[comp], + title=f"{key} - EOG Component ({label.replace('_', ' ')})", + show=True, + ) From ad522e81ae5ba024d13a3ff45b31b784a8eecf5d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:34:48 +0000 Subject: [PATCH 10/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/preprocessing/ica_comparison.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index 6c2888ea95b..57025085435 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -25,15 +25,15 @@ # %% +import warnings from time import time -import warnings import numpy as np +from sklearn.exceptions import ConvergenceWarning import mne from mne.datasets import sample from mne.preprocessing import ICA -from sklearn.exceptions import ConvergenceWarning print(__doc__) From 77f1f3a2397fd6a78f955a349b082a154bb858b5 Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Thu, 11 Sep 2025 18:21:10 +0530 Subject: [PATCH 11/15] Add ICA algorithm comparison example with noise robustness evaluation --- examples/preprocessing/ica_comparison.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index 57025085435..cf68061d913 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -25,15 +25,15 @@ # %% -import warnings from time import time +import warnings import numpy as np -from sklearn.exceptions import ConvergenceWarning import mne from mne.datasets import sample from mne.preprocessing import ICA +from sklearn.exceptions import ConvergenceWarning print(__doc__) @@ -179,7 +179,8 @@ def run_ica( # Emit informational lines similar to MNE's verbose output n_channels = raw_input.info["nchan"] print( - f"Fitting ICA to data using {n_channels} channels (please be patient, this may take a while)" + f"Fitting ICA to data using {n_channels} channels" + f"(please be patient, this may take a while)" ) print("Selecting by number: 20 components") t0 = time() @@ -200,7 +201,10 @@ def run_ica( print(f"Fitting ICA took {fit_time:.1f}s.") data_label = label if label is not None else "data" - title = f"ICA decomposition using {name_for_print} on {data_label}\n(took {fit_time:.1f}s)" + title = ( + f"ICA decomposition using {name_for_print} on {data_label}\n" + f"(took {fit_time:.1f}s)" + ) ica.plot_components(title=title) return ica, fit_time @@ -245,7 +249,7 @@ def run_all_ica(raw_input, label, reject): # %% -# Build noisy datasets for each SNR level and noise type with associated reject thresholds +# Build noisy datasets for each SNR level and noise type noisy_sets = {} idx = 0 for snr_db in snr_levels: From 55c8f135e8db31d2dbd9ff6f0a3bbcf3a7b9e8d9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:51:30 +0000 Subject: [PATCH 12/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/preprocessing/ica_comparison.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index cf68061d913..1b6f56aefa3 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -25,15 +25,15 @@ # %% +import warnings from time import time -import warnings import numpy as np +from sklearn.exceptions import ConvergenceWarning import mne from mne.datasets import sample from mne.preprocessing import ICA -from sklearn.exceptions import ConvergenceWarning print(__doc__) From 9214d09971c8219d98d0a3eedbe7bf73d62face8 Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Thu, 11 Sep 2025 20:13:32 +0530 Subject: [PATCH 13/15] DOC: Add ICA algorithm comparison example with noise robustness evaluation --- examples/preprocessing/ica_comparison.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index 1b6f56aefa3..8f00210e856 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -27,7 +27,7 @@ import warnings from time import time - +import matplotlib.pyplot as plt import numpy as np from sklearn.exceptions import ConvergenceWarning @@ -206,6 +206,7 @@ def run_ica( f"(took {fit_time:.1f}s)" ) ica.plot_components(title=title) + plt.close() return ica, fit_time From 149ed7f11fe4cdf56e15df9901563549da6506d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:43:55 +0000 Subject: [PATCH 14/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/preprocessing/ica_comparison.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index 8f00210e856..60539eb99fc 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -27,6 +27,7 @@ import warnings from time import time + import matplotlib.pyplot as plt import numpy as np from sklearn.exceptions import ConvergenceWarning From 47ab1e6c3044957f489e833bef12c9ab87e12110 Mon Sep 17 00:00:00 2001 From: KALLA GANASEKHAR Date: Thu, 11 Sep 2025 22:03:04 +0530 Subject: [PATCH 15/15] Update ica_comparison.py --- examples/preprocessing/ica_comparison.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/preprocessing/ica_comparison.py b/examples/preprocessing/ica_comparison.py index 60539eb99fc..0b93c5a30be 100644 --- a/examples/preprocessing/ica_comparison.py +++ b/examples/preprocessing/ica_comparison.py @@ -293,6 +293,7 @@ def run_all_ica(raw_input, label, reject): icas_all[key].plot_components( picks=[comp], title=f"{key} - EOG Component (Clean Data)", show=True ) + plt.close() # %% @@ -307,3 +308,4 @@ def run_all_ica(raw_input, label, reject): title=f"{key} - EOG Component ({label.replace('_', ' ')})", show=True, ) + plt.close()