Skip to content
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

Allow baseline time window selection from other events #11480

Open
Aaronearlerichardson opened this issue Feb 15, 2023 · 6 comments
Open

Allow baseline time window selection from other events #11480

Aaronearlerichardson opened this issue Feb 15, 2023 · 6 comments
Labels

Comments

@Aaronearlerichardson
Copy link
Contributor

Describe the new feature or enhancement

Currently, all of the baseline inputs (for example in the Epochs class) require a tuple of start and end times. This assumes you want to calculate your baseline with respect to the same event which you are epoching from.

Lets say I have two events: 'e1' and 'e2'. I want to create an epoch that goes from -1s to 2 seconds around 'e2' with a baseline correction from -0.5s to 0 with respect to 'e1'

events, event_id = mne.events_from_annotations(raw)
e2 = mne.Epochs(raw, events, event_id['e2'], tmin=-1, tmax=1.5, baseline=None)
baseline = mne.Epochs(raw, events, event_id['e1'], tmin=-0.5, tmax=0, baseline=None)

e2 is the time frame I'm interested in, and baseline is the data I'd like to use for baseline correction. Setting baseline=(-0.5,0) would only do so with respect to 'e2'.

Describe your proposed implementation

e2 = mne.Epochs(raw, events, event_id['e2'], tmin=-1, tmax=1.5, baseline={event_id['e1'] : (-0.5,0)})

Describe possible alternatives

e2 = mne.Epochs(raw, events, event_id['e2'], tmin=-1, tmax=1.5, baseline=(-0.5,0), baseline_event=event_id['e1'])
e2 = mne.Epochs(raw, events, event_id['e2'], tmin=-1, tmax=1.5, baseline=(event_id['e1'],-0.5,0))

Additional context

No response

@Aaronearlerichardson
Copy link
Contributor Author

This is also a problem in the current implementation of baseline.rescale

@larsoner
Copy link
Member

Describe possible alternatives

For this I would actually use epochs.apply_function(...), I think you should be able to get it to do what you want. Per-epoch baselining based on some other time interval doesn't map nicely into our apply_baseline scheme, so I think a custom approach like this is better.

This is also a problem in the current implementation of baseline.rescale

Can you elaborate on this a bit? We should fix bugs with this

@Aaronearlerichardson
Copy link
Contributor Author

For this I would actually use epochs.apply_function(...)

Does apply_function average across all Epochs?

Can you elaborate on this a bit? We should fix bugs with this

It's not a bug. There's just no way to use baseline.rescale if you want to use an alternative numpy array for baseline. I rewrote the original code for my own purposes, but you could use it here if someone want to pass an array instead of a tuple of times for the baseline input.

def rescale(data: np.ndarray, basedata: np.ndarray, mode: str = 'mean',
            copy: bool = True) -> np.ndarray:
    """Rescale (baseline correct) data.
    Parameters
    ----------
    data : array
        It can be of any shape. The only constraint is that the last
        dimension should be time.
    basedata : array
        It can be of any shape. The last dimension should be time, and the
        first dimension should equal data.
    %(baseline_rescale)s
    mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio'
        Perform baseline correction by
        - subtracting the mean of baseline values ('mean')
        - dividing by the mean of baseline values ('ratio')
        - dividing by the mean of baseline values and taking the log
          ('logratio')
        - subtracting the mean of baseline values followed by dividing by
          the mean of baseline values ('percent')
        - subtracting the mean of baseline values and dividing by the
          standard deviation of baseline values ('zscore')
        - dividing by the mean of baseline values, taking the log, and
          dividing by the standard deviation of log baseline values
          ('zlogratio')
    copy : bool
        Whether to return a new instance or modify in place.
    Returns
    -------
    data_scaled: array
        Array of same shape as data after rescaling.
    """
    if copy:
        data = data.copy()

    match mode:
        case 'mean':
            def fun(d, m, s):
                d -= m
        case 'ratio':
            def fun(d, m, s):
                d /= m
        case 'logratio':
            def fun(d, m, s):
                d /= m
                np.log10(d, out=d)
        case 'percent':
            def fun(d, m, s):
                d -= m
                d /= m
        case 'zscore':
            def fun(d, m, s):
                d -= m
                d /= s
        case 'zlogratio':
            def fun(d, m, s):
                d /= m
                np.log10(d, out=d)
                d /= s
        case _:
            raise NotImplementedError()

    mean = np.mean(basedata, axis=-1, keepdims=True)
    std = np.std(basedata, axis=-1, keepdims=True)
    fun(data, mean, std)
    return data

@larsoner
Copy link
Member

For this I would actually use epochs.apply_function(...)

Does apply_function average across all Epochs?

Is it clear from https://mne.tools/stable/generated/mne.Epochs.html#mne.Epochs.apply_function ? If not then we should update the docs...

@hoechenberger
Copy link
Member

@Aaronearlerichardson is this issue still of concern to you, or can it be closed?

@cbrnr
Copy link
Contributor

cbrnr commented Aug 24, 2023

I think this is a fairly common use case in ERP analysis when performing response-locked averaging (i.e. around event e2). In this case, the baseline will usually be at the beginning of a trial, i.e. around (before) some other event e1. Therefore, I think it would be extremely helpful if we included such an example in the docstring, so people don't have to reinvent the wheel.

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

No branches or pull requests

4 participants