Skip to content

ica.plot_properties(reject='auto') raises IndexError when fit-time reject dropped any segments #13879

@pjdurka

Description

@pjdurka

Description

Calling ica.plot_properties(raw) (or ica.plot_properties(raw, reject='auto'),
the default) raises IndexError: index N is out of bounds for axis 0 with size N-K for every component whenever the underlying raw contains
at least one 2-second window whose peak-to-peak amplitude exceeds the
fit-time reject threshold (ica.reject_).

This affects the report path too: mne.Report.add_ica(...) propagates the
same error (and on Windows with joblib's loky backend, the traceback gets
rewritten to "Could not pickle the task to send it to the workers").

PR #13746 (released in 1.12) addressed a related failure mode but didn't
fix this one — the OOB just moved from epoch_var[dropped_indices] to
drop_var[dropped_indices] (1.12+ line 666 of mne/viz/ica.py).

Steps to reproduce (no external data)

import numpy as np                                        
import mne
from mne.preprocessing import ICA

sfreq, duration = 250.0, 60.0
n = int(sfreq * duration)                                                                                                    
ch_names = ["Fp1","Fp2","F7","F3","Fz","F4","F8","T3","C3","Cz",
            "C4","T4","T5","P3","Pz","P4","T6","O1","O2"]                                                                    
                                                          
rng = np.random.default_rng(0)                                                                                               
data = rng.standard_normal((19, n)) * 3e-6                                                                                   
# 5 mV spike in the LAST 2-sec window, so the bad-window epoch index
# is the maximum possible (= N), guaranteeing OOB on length-(N-1)                                                            
# post-rejection arrays.                                  
data[0, n - int(0.5*sfreq) : n - int(0.4*sfreq)] += 5e-3                                                                     
                                                          
raw = mne.io.RawArray(                                                                                                       
    data, mne.create_info(ch_names, sfreq, "eeg"), verbose=False)                                                            
raw.set_montage("standard_1020")
                                                                                                                             
ica = ICA(n_components=5, method="picard", random_state=0,
          fit_params=dict(ortho=False, extended=True))                                                                       
ica.fit(raw, reject=dict(eeg=500e-6))   # spike exceeds 500 µV -> drops window
                                                                                                                             
ica.plot_properties(raw, picks=[0], show=False)           
# IndexError: index 30 is out of bounds for axis 0 with size 29                                                              
                                                                                                                             
Expected behavior
                                                                                                                             
Either skip the dropped-segment markers gracefully or compute their                                                          
positions in the same index space as the per-epoch arrays.
                                                                                                                             
Actual behavior                                                                                                              

IndexError for every component. Affects ica.plot_properties,                                                                 
mne.Report.add_ica, and the click-to-inspect callback from
ica.plot_components.                                                                                                         

Root cause                                                                                                                   
                                                          
In mne/viz/ica.py::_prepare_data_ica_properties, when inst is Raw                                                            
and reject='auto' (which uses ica.reject_):
                                                                                                                             
1. _reject_data_segments(data, reject, ..., tstep=2) returns                                                                 
drop_indssample-index pairs into the original signal.                                                                     
2. The post-rejection data is wrapped into a shorter RawArray and                                                            
epoched into epochs_src with N - K epochs (where K = len(drop_inds)).                                                        
3. dropped_indices = [(d[0] // len(epochs_src.times)) + 1 for d in drop_inds]                                                
computes original-space epoch numbers (values up to N).                                                                      
4. In _plot_ica_properties / _fast_plot_ica_properties, those indices                                                        
are used to subscript epoch_var / drop_var, which were computed                                                              
from epochs_src and therefore live in post-rejection space                                                                   
(length N - K).                                                                                                              
                                                                                                                             
epoch_var[dropped_indices] (≤1.11) and drop_var[dropped_indices]                                                             
(1.12+) overflow by exactly K whenever K1.                                                                                
                                                                                                                             
Worked example (60 s @ 250 Hz, 1 bad window at the end)                                                                      
                                                                                                                             
epoch_len = 500 samples, N = 30 epochs total                                                                                 
bad window samples = [14500, 15000) -> d[0] = 14500                                                                          
dropped_indices = [14500 // 500 + 1] = [30]                                                                                  
post-rejection length = 29                                                                                                   
drop_var[30] -> index 30 out of bounds for axis 0 with size 29                                                               
                                                                                                                             
Suggested fix
                                                                                                                             
The intent at line 663666 (on main) is "splice zero-variance entries                                                        
back into epoch_var at the original-space drop positions". drop_var
is built from dropped_src[idx] where dropped_src is the dropped_seg                                                          
returned by _reject_data_segments, and indexed with the                                                                      
post-rejection-space np.arange(K) — not with dropped_indices.                                                                
So:                                                                                                                          
                                                                                                                             
# current (1.12, mne/viz/ica.py:660-668):                                                                                    
epoch_var = np.insert(                                                                                                       
    arr=epoch_var,
    obj=insertion_obj,                                                                                                       
    values=drop_var[dropped_indices],   # OOB             
    axis=0,                                                                                                                  
)
                                                                                                                             
# fix:                                                    
epoch_var = np.insert(
    arr=epoch_var,
    obj=insertion_obj,                                                                                                       
    values=drop_var,                    # already in 0..K-1 space
    axis=0,                                                                                                                  
)                                                         
                                                                                                                             
(I haven't traced the full data flow to verify drop_var length equals                                                        
K in every edge case)
                                                                                                                             
Versions                                                  
                                                                                                                             
Reproduced on 1.11.0, 1.12.0, 1.12.1, and main (HEAD as of 2026-05-01).        

Diagnosis and reproducer drafted with [Claude Code](https://claude.com/claude-code) (Anthropic, Claude Opus 4.7)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions