Aluode/PerceptionLabPortable
0
1"""Utility functions to baseline-correct data."""2 3# Authors: The MNE-Python contributors.4# License: BSD-3-Clause5# Copyright the MNE-Python contributors.6 7import numpy as np8 9from .utils import _check_option, _validate_type, logger, verbose10 11 12def _log_rescale(baseline, mode="mean"):13 """Log the rescaling method."""14 if baseline is not None:15 _check_option(16 "mode",17 mode,18 ["logratio", "ratio", "zscore", "mean", "percent", "zlogratio"],19 )20 msg = f"Applying baseline correction (mode: {mode})"21 else:22 msg = "No baseline correction applied"23 return msg24 25 26@verbose27def rescale(data, times, baseline, mode="mean", copy=True, picks=None, verbose=None):28 """Rescale (baseline correct) data.29 30 Parameters31 ----------32 data : array33 It can be of any shape. The only constraint is that the last34 dimension should be time.35 times : 1D array36 Time instants is seconds.37 %(baseline_rescale)s38 mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio'39 Perform baseline correction by40 41 - subtracting the mean of baseline values ('mean')42 - dividing by the mean of baseline values ('ratio')43 - dividing by the mean of baseline values and taking the log44 ('logratio')45 - subtracting the mean of baseline values followed by dividing by46 the mean of baseline values ('percent')47 - subtracting the mean of baseline values and dividing by the48 standard deviation of baseline values ('zscore')49 - dividing by the mean of baseline values, taking the log, and50 dividing by the standard deviation of log baseline values51 ('zlogratio')52 53 copy : bool54 Whether to return a new instance or modify in place.55 picks : list of int | None56 Data to process along the axis=-2 (None, default, processes all).57 %(verbose)s58 59 Returns60 -------61 data_scaled: array62 Array of same shape as data after rescaling.63 """64 if copy:65 data = data.copy()66 if verbose is not False:67 msg = _log_rescale(baseline, mode)68 logger.info(msg)69 if baseline is None or data.shape[-1] == 0:70 return data71 72 bmin, bmax = baseline73 if bmin is None:74 imin = 075 else:76 imin = np.where(times >= bmin)[0]77 if len(imin) == 0:78 raise ValueError(79 f"bmin is too large ({bmin}), it exceeds the largest time value"80 )81 imin = int(imin[0])82 if bmax is None:83 imax = len(times)84 else:85 imax = np.where(times <= bmax)[0]86 if len(imax) == 0:87 raise ValueError(88 f"bmax is too small ({bmax}), it is smaller than the smallest time "89 "value"90 )91 imax = int(imax[-1]) + 192 if imin >= imax:93 raise ValueError(94 f"Bad rescaling slice ({imin}:{imax}) from time values {bmin}, {bmax}"95 )96 97 # technically this is inefficient when `picks` is given, but assuming98 # that we generally pick most channels for rescaling, it's not so bad99 mean = np.mean(data[..., imin:imax], axis=-1, keepdims=True)100 101 if mode == "mean":102 103 def fun(d, m):104 d -= m105 106 elif mode == "ratio":107 108 def fun(d, m):109 d /= m110 111 elif mode == "logratio":112 113 def fun(d, m):114 d /= m115 np.log10(d, out=d)116 117 elif mode == "percent":118 119 def fun(d, m):120 d -= m121 d /= m122 123 elif mode == "zscore":124 125 def fun(d, m):126 d -= m127 d /= np.std(d[..., imin:imax], axis=-1, keepdims=True)128 129 elif mode == "zlogratio":130 131 def fun(d, m):132 d /= m133 np.log10(d, out=d)134 d /= np.std(d[..., imin:imax], axis=-1, keepdims=True)135 136 if picks is None:137 fun(data, mean)138 else:139 for pi in picks:140 fun(data[..., pi, :], mean[..., pi, :])141 return data142 143 144def _check_baseline(baseline, times, sfreq, on_baseline_outside_data="raise"):145 """Check if the baseline is valid and adjust it if requested.146 147 ``None`` values inside ``baseline`` will be replaced with ``times[0]`` and148 ``times[-1]``.149 150 Parameters151 ----------152 baseline : array-like, shape (2,) | None153 Beginning and end of the baseline period, in seconds. If ``None``,154 assume no baseline and return immediately.155 times : array156 The time points.157 sfreq : float158 The sampling rate.159 on_baseline_outside_data : 'raise' | 'info' | 'adjust'160 What to do if the baseline period exceeds the data.161 If ``'raise'``, raise an exception (default).162 If ``'info'``, log an info message.163 If ``'adjust'``, adjust the baseline such that it is within the data range.164 165 Returns166 -------167 (baseline_tmin, baseline_tmax) | None168 The baseline with ``None`` values replaced with times, and with adjusted times169 if ``on_baseline_outside_data='adjust'``; or ``None``, if ``baseline`` is170 ``None``.171 """172 if baseline is None:173 return None174 175 _validate_type(baseline, "array-like")176 baseline = tuple(baseline)177 178 if len(baseline) != 2:179 raise ValueError(180 f"baseline must have exactly two elements (got {len(baseline)})."181 )182 183 tmin, tmax = times[0], times[-1]184 tstep = 1.0 / float(sfreq)185 186 # check default value of baseline and `tmin=0`187 if baseline == (None, 0) and tmin == 0:188 raise ValueError(189 "Baseline interval is only one sample. Use `baseline=(0, 0)` if this is "190 "desired."191 )192 193 baseline_tmin, baseline_tmax = baseline194 195 if baseline_tmin is None:196 baseline_tmin = tmin197 baseline_tmin = float(baseline_tmin)198 199 if baseline_tmax is None:200 baseline_tmax = tmax201 baseline_tmax = float(baseline_tmax)202 203 if baseline_tmin > baseline_tmax:204 raise ValueError(205 f"Baseline min ({baseline_tmin}) must be less than baseline max ("206 f"{baseline_tmax})"207 )208 209 if (baseline_tmin < tmin - tstep) or (baseline_tmax > tmax + tstep):210 msg = (211 f"Baseline interval [{baseline_tmin}, {baseline_tmax}] s is outside of "212 f"epochs data [{tmin}, {tmax}] s. Epochs were probably cropped."213 )214 if on_baseline_outside_data == "raise":215 raise ValueError(msg)216 elif on_baseline_outside_data == "info":217 logger.info(msg)218 elif on_baseline_outside_data == "adjust":219 if baseline_tmin < tmin - tstep:220 baseline_tmin = tmin221 if baseline_tmax > tmax + tstep:222 baseline_tmax = tmax223 224 return baseline_tmin, baseline_tmax225 