Ejemplo n.º 1
0
def noisy_ev(eeg_data):
    """
    Indentify noisy events based on the scores:

    1. **Range score (r)**: Clean EEG signals have a voltage range within some
       specific limits. Ranges off these limits are a signal of noise presence.
    2. **Deviation score (d)**: High deviations of voltage from the average
       value are also noise indicators.
    3. **Variance score (v)**:  Extremely high voltage variances are also
       indicators of noise.

    Parameters
    ----------
    eeg_data : array
        EEG data in a CxTxE array. With C the number of channels, T the number
        of time samples and E the number of events.

    Returns
    -------
    coefs : dict
        Dictionary with the resulting scores arrays 'r', 'd' and 'v', each of
        length #events.
    """

    # -------------------------------------------------------------------------
    # 1. Compute trial scores

    # 1.1 Channel-average voltage range
    r_coef = (eeg_data.max(axis=t_dim) - eeg_data.min(axis=t_dim))\
        .mean(axis=ch_dim)

    # 1.2 Channel-average voltage deviation for mean voltage
    d_coef = np.squeeze((eeg_data.mean(axis=t_dim, keepdims=True) -
                         eeg_data.mean(axis=(t_dim, ev_dim), keepdims=True)
                         ).mean(axis=ch_dim))

    # 1.3 Voltage variance
    v_coef = eeg_data.var(axis=t_dim).mean(axis=ch_dim)

    # -------------------------------------------------------------------------
    # Classify trials

    # Detect outliers
    r_outliers = mixnp.zscore_outliers(r_coef)[0]
    d_outliers = mixnp.zscore_outliers(d_coef)[0]
    v_outliers = mixnp.zscore_outliers(v_coef)[0]

    # Use an OR operator to compute the final result
    return np.where(r_outliers | d_outliers | v_outliers)
Ejemplo n.º 2
0
def artifact_rejection_with_lcf(eeg_data, fs,
            freq=(0.5, None),
            ref_ch=None, eeg_ch=None):
    """
    Example function covering the steps defined in :ref:`step_by_step`.

    1. **Filter the data**: FIR filters are applied within the specified range
       (*freq*).

    2. **Check reference channel**: Re-reference the data to the specified
       channel (*ref_ch*).

    3. **Remove the baseline**: Baseline is computed using function
       :func:`eeglcf.mixnp.zscore_outliers`, which ignores outliers.

    4. **Reject noisy EEG channels**: Channel rejected using sample function
       :func:`eeglcf.lazy.noisy_ch`.

    5. **Reject noisy EEG trials**: Trial rejected using sample function
       :func:`eeglcf.lazy.noisy_ev`.

    6. **Apply the BSS algorithm**: `FastICA`_ algorithm is applied as the BSS
       technique, via the sample function :func:`eeglcf.lazy.fastica`.

    7. **Identify artifactual components**: In this case, we do not apply this
       step. Hence, the architecture corresponds to the application of the LCF
       method by itself.

    8. **Process artifactual components**: All 0s components are used as
       alternative components.

    9. **Apply LCF**: This is:

       .. code-block:: python

         c_data = eeglcf.lcf(b_data, a_data)

       where *b_data* is a numpy.ndarray containing the result of applying a BSS method
       to EEG data, and *a_data* is an alternative "cleaner" version of the previous.
       Both variable have dimensions CxTxE, where C, T and E are the number of
       channels, time samples and events respectively.

    10. **Apply the BSS^{-1} algorithm**: Back-project `FastICA`_ via the
        sample function :func:`eeglcf.lazy.inv_fastica`.

    11. **Remove baseline**. Baseline is computed using function
       :func:`eeglcf.mixnp.zscore_outliers`, which ignores outliers.

    12. **Reject channels inside events**: Not coded.

    13. **Interpolate channels**: Not coded.

    Parameters
    ----------
    eeg_data : array
        EEG data.
    fs : float
        EEG sampling frequency.
    freq : tuple
        Tuple with the lower and higher cut-off frequencies respectively. If
        freq[0] is None, no high-pass filter is applied. If freq[1] is None, no
        low-pass filter is applied.
    ref_ch : int, None
        Index of the EEG reference channel. If None, the EEG will be
        re-referenced to channel 0.
    eeg_ch : list, None
        List of the channel indices corresponding to EEG signals (i.e.
        excluding EOG, EMG, ...). If None, all channels will be considered EEG
        channels.

    Returns
    -------
    res : array
        Clean EEG data

    """

    # Set the dimensions of eeg_data
    ch_dim = 0
    t_dim = 1

    # -------------------------------------------------------------------------
    # 1. Filter the data to remove uninteresting frequencies, which can
    #    potentially carry noise.

    if freq[0] is not None:  # High-pass filter the data
        # Design the filter. We will use a FIR filter of order 100 and a
        # Hamming window
        if freq[0] > 2:  # Set a transition band of 2 Hz.
            x_freq = [0, freq[0]/fs, (freq[0]-2)/fs, 1]
            gain = [0, 0, 1, 1]
        else:  # freq[0] <= 2  # No transition band
            x_freq = [0, freq[0]/fs, 1]
            gain = [0, 1, 1]
        filt_ba = [sp_signal.firwin2(numtaps=100, freq=x_freq, gain=gain,
                                     window='hamming', antisymmetric=True),
                   np.array([1])]
        # High-pass filtered data
        hpf_data = sp_signal.filtfilt(b=filt_ba[0], a=filt_ba[1], x=eeg_data,
                                      axis=t_dim, padtype='odd',
                                      padlen=len(filt_ba[0]))
    else:  # Do not filter
        hpf_data = eeg_data

    if freq[1] is not None:  # Low-pass filter the data
        # Design the filter. We will use a FIR filter of order 101 and a
        # Hamming window
        if freq[1]+2 < fs:  # Use a transition band of 2 Hz
            x_freq = [0, freq[1]/fs, (freq[1]+2)/fs, 1]
            gain = [1, 1, 0, 0]
        else:  # freq[1]+2 >= fs  # No transition band
            x_freq = [0, freq[1]/eeg_data.fs, 1]
            gain = [1, 1, 0]
        filt_ba = [sp_signal.firwin2(numtaps=101, freq=x_freq, gain=gain,
                                     window='hamming', antisymmetric=False),
                   np.array([1])]
        # Low-pass filtered data
        f_data = sp_signal.filtfilt(b=filt_ba[0], a=filt_ba[1], x=hpf_data,
                                    axis=t_dim, padtype='odd',
                                    padlen=len(filt_ba[0]))

    else:  # Do not filter
        f_data = hpf_data

    # -------------------------------------------------------------------------
    # 2. Make sure that the data has a reference channel.
    #
    #    Avoid common average reference before cleaning the signal, as it will
    # spread the noise throughout all channels.
    #    Some systems, such as BIOSEMI, provide signal without reference.

    if ref_ch is None:  # Re-reference the data to channel 0
        ref_ch = 0
        ref_data = f_data - f_data.take([ref_ch], axis=ch_dim)
    else:
        ref_data = f_data

    # -------------------------------------------------------------------------
    # 3. Remove the baseline

    # We compute the average rejecting outliers
    rb_data = ref_data - mixnp.zscore_outliers(ref_data, axis=t_dim, keep_dims=True)[1]

    # -------------------------------------------------------------------------
    # 4. Reject noisy EEG channels

    # Apply your preferred method for channel rejection. In this case, we will
    # use *noisy_ch* function as an example.
    if eeg_ch is None:  # All channels are EEG channels
        eeg_ch = range(rb_data.shape[ch_dim])
    sel_chs = np.array([ch_n for ch_n in range(rb_data.shape[ch_dim])
                        if (ch_n != ref_ch) and (ch_n in eeg_ch)])
    rej_ch = noisy_ch(rb_data.take(sel_chs, axis=ch_dim))
    # Adapt the list of rejected channels to consider all channels
    rej_ch = sel_chs[rej_ch]
    # Remove channels
    kept_ch = [ch_n for ch_n in range(rb_data.shape[ch_dim])
               if ch_n not in rej_ch]
    ch_data = rb_data.take(kept_ch, axis=ch_dim)

    # You should keep the list of rejected channels if you wish to interpolate
    # them back into the data later on.

    # -------------------------------------------------------------------------
    # 5. Reject noisy EEG events/trials

    # Apply your preferred method for event rejection. We will use the support
    # function *noisy_ev*.
    rej_ev = noisy_ev(ch_data)
    kept_ev = [ev_n for ev_n in range(rb_data.shape[ev_dim])
               if ev_n not in rej_ev]
    ev_data = ch_data.take(kept_ev, axis=ev_dim)

    # -------------------------------------------------------------------------
    # 6. Apply the BSS algorithm

    ica, comp = fastica(ev_data)

    # -------------------------------------------------------------------------
    # 7. Apply the artifactual component detection and processing algorithm

    # In this example, we do not apply any artifactual component detection or
    # processing algorithm. We evaluate all components with LCF using all 0s
    # alternative signals.

    pp_comp = None
    rej_comps = None

    # -------------------------------------------------------------------------
    # 8. Apply LCF

    if rej_comps is None:  # All components considered
        lcf_comp = lcf(comp, pp_comp)

    else:  # Apply LCF only to the rejected components
        rej_slice = [None]*3
        rej_slice[ch_dim] = rej_comps
        lcf_comp = copy.copy(comp)
        lcf_comp[rej_slice] = lcf(comp[rej_slice], pp_comp[rej_slice])

    # -------------------------------------------------------------------------
    # 9. Back-project to the original space

    rc_data = inv_fastica(ica, lcf_comp)

    # -------------------------------------------------------------------------
    # 10. Remove baseline

    # The baseline can be offset by the BSS processing. It is recommended to
    # re-remove the baseline
    rb_data = rc_data - mixnp.zscore_outliers(rc_data, axis=t_dim,
                                              keep_dims=True)[1]

    # -------------------------------------------------------------------------
    # 11. Reject channels inside events
    #
    #   Analyze channels within each event to reject and interpolate those that
    # are still too noisy. Note that the interpolation is also done within
    # individual events.

    pass

    # -------------------------------------------------------------------------
    # 12. Interpolate channels rejected in step 4

    pass

    return rb_data
Ejemplo n.º 3
0
def noisy_ch(eeg_data, ref_ch=None):
    """
    Identify noisy channels based on the following scores:

    1. **Correlation score (r)**: Clean EEG signals are highly correlation
       across channels. Therefore, a noise descriptor for a channels might be a
       low average correlation with other channels.

    2. **Variance score (v)**: Extremely high event-average time-variances are
       also indicators of noise.

    3. **Hurst score (h)**: Clean EEG signals has very specific Hurst exponent
       values. Deviations from this point are also noise indicators.

    Outliers are labeled as noisy channels. They are detected based on the
    z-score of each of the above measurements (z-score > 3).

    Parameters
    ----------
    eeg_data : array
        EEG data in a CxTxE array. With C the number of channels, T the number
        of time samples and E the number of events.
    ref_ch : int, None
        Index of the reference channel.

    Returns
    -------
    noisy_ch : list
        List with the index of the channel identified as noisy.
    """
    # TODO: Distance to reference channel
    # When the reference channel is specified in the calling instance, the
    # first two scores are corrected for the quadratic effect of the distance
    # to this reference channel.

    # Dimension shapes
    ch_len = eeg_data.shape[ch_dim]
    t_len = eeg_data.shape[t_dim]
    ev_len = eeg_data.shape[ev_dim]

    # -------------------------------------------------------------------------
    # 1. Compute channel scores

    # 1.1 Average time correlation:
    # We need to collapse time and events dimensions
    coll_eeg = eeg_data.transpose([t_dim, ev_dim, ch_dim])\
        .reshape([t_len*ev_len, ch_len])
    # coll_eeg.shape = (time&events)x(channels)
    r_coef = np.abs(mixnp.pearson(coll_eeg).mean(axis=1))

    # 1.2 Time variance
    v_coef = coll_eeg.var(axis=0)

    # 1.3 Hurst exponent
    h_coef = np.zeros(ch_len)
    for ch_n in range(ch_len):
        h_coef[ch_n] = mixnp.hurst(coll_eeg[:, ch_n])
    del coll_eeg

    # Remove quadratic effect of distance to reference channel
    if ref_ch is not None:
        # Compute distance to the reference data to correct the scores
        dist_to_ref = np.squeeze(eeg_data.ch_dist(ch=ref_ch))

        # Correlation score
        poly_coef = np.polyfit(dist_to_ref, r_coef, 2)
        r_coef -= np.polyval(poly_coef, dist_to_ref)
        del poly_coef

        # Variance score
        poly_coef = np.polyfit(dist_to_ref, v_coef, 2)
        v_coef -= np.polyval(poly_coef, dist_to_ref)

    # -------------------------------------------------------------------------
    # Classify channels

    # Detect outliers
    r_outliers = mixnp.zscore_outliers(r_coef)[0]
    v_outliers = mixnp.zscore_outliers(v_coef)[0]
    h_outliers = mixnp.zscore_outliers(h_coef)[0]

    # Use an OR operator to compute the final result
    return np.where(r_outliers | v_outliers | h_outliers)[0]