Skip to content

Commit d061702

Browse files
cbrnrdrammocklarsoner
authored andcommitted
Add raw.rescale (mne-tools#13018)
Co-authored-by: Daniel McCloy <[email protected]> Co-authored-by: Eric Larson <[email protected]>
1 parent 7097dad commit d061702

File tree

5 files changed

+89
-0
lines changed

5 files changed

+89
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add new :meth:`Raw.rescale <mne.io.Raw.rescale>` method to rescale the data in place, by `Clemens Brunner`_.

doc/sphinxext/related_software.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@
8181
"Summary": "A graphical user interface for MNE",
8282
},
8383
# TODO: these do not set a valid homepage or documentation page on PyPI
84+
"eeg_positions": {
85+
"Home-page": "https://eeg-positions.readthedocs.io",
86+
"Summary": "Compute and plot standard EEG electrode positions.",
87+
},
8488
"mne-features": {
8589
"Home-page": "https://mne.tools/mne-features",
8690
"Summary": "MNE-Features software for extracting features from multivariate time series", # noqa: E501

mne/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ def pytest_configure(config):
178178
ignore:__array__ implementation doesn't accept a copy.*:DeprecationWarning
179179
# quantities via neo
180180
ignore:The 'copy' argument in Quantity is deprecated.*:
181+
# debugpy uses deprecated matplotlib API
182+
ignore:The (non_)?interactive_bk attribute was deprecated.*:
181183
""" # noqa: E501
182184
for warning_line in warning_lines.split("\n"):
183185
warning_line = warning_line.strip()

mne/io/base.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,71 @@ def resample(
15001500
)
15011501
return self, events
15021502

1503+
@verbose
1504+
def rescale(self, scalings, *, verbose=None):
1505+
"""Rescale channels.
1506+
1507+
.. warning::
1508+
MNE-Python assumes data are stored in SI base units. This function should
1509+
typically only be used to fix an incorrect scaling factor in the data to get
1510+
it to be in SI base units, otherwise unintended problems (e.g., incorrect
1511+
source imaging results) and analysis errors can occur.
1512+
1513+
Parameters
1514+
----------
1515+
scalings : int | float | dict
1516+
The scaling factor(s) by which to multiply the data. If a float, the same
1517+
scaling factor is applied to all channels (this works only if all channels
1518+
are of the same type). If a dict, the keys must be valid channel types and
1519+
the values the scaling factors to apply to the corresponding channels.
1520+
%(verbose)s
1521+
1522+
Returns
1523+
-------
1524+
raw : Raw
1525+
The raw object with rescaled data (modified in-place).
1526+
1527+
Examples
1528+
--------
1529+
A common use case for EEG data is to convert from µV to V, since many EEG
1530+
systems store data in µV, but MNE-Python expects the data to be in V. Therefore,
1531+
the data needs to be rescaled by a factor of 1e-6. To rescale all channels from
1532+
µV to V, you can do::
1533+
1534+
>>> raw.rescale(1e-6) # doctest: +SKIP
1535+
1536+
Note that the previous example only works if all channels are of the same type.
1537+
If there are multiple channel types, you can pass a dict with the individual
1538+
scaling factors. For example, to rescale only EEG channels, you can do::
1539+
1540+
>>> raw.rescale({"eeg": 1e-6}) # doctest: +SKIP
1541+
"""
1542+
_validate_type(scalings, (int, float, dict), "scalings")
1543+
_check_preload(self, "raw.rescale")
1544+
1545+
channel_types = self.get_channel_types(unique=True)
1546+
1547+
if isinstance(scalings, int | float):
1548+
if len(channel_types) == 1:
1549+
self.apply_function(lambda x: x * scalings, channel_wise=False)
1550+
else:
1551+
raise ValueError(
1552+
"If scalings is a scalar, all channels must be of the same type. "
1553+
"Consider passing a dict instead."
1554+
)
1555+
else:
1556+
for ch_type in scalings.keys():
1557+
if ch_type not in channel_types:
1558+
raise ValueError(
1559+
f'Channel type "{ch_type}" is not present in the Raw file.'
1560+
)
1561+
for ch_type, ch_scale in scalings.items():
1562+
self.apply_function(
1563+
lambda x: x * ch_scale, picks=ch_type, channel_wise=False
1564+
)
1565+
1566+
return self
1567+
15031568
@verbose
15041569
def crop(self, tmin=0.0, tmax=None, include_tmax=True, *, verbose=None):
15051570
"""Crop raw data file.

mne/io/tests/test_raw.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,3 +1063,20 @@ def test_last_samp():
10631063
raw = read_raw_fif(raw_fname).crop(0, 0.1).load_data()
10641064
last_data = raw._data[:, [-1]]
10651065
assert_array_equal(raw[:, -1][0], last_data)
1066+
1067+
1068+
def test_rescale():
1069+
"""Test rescaling channels."""
1070+
raw = read_raw_fif(raw_fname, preload=True) # multiple channel types
1071+
1072+
with pytest.raises(ValueError, match="If scalings is a scalar, all channels"):
1073+
raw.rescale(2) # need to use dict
1074+
1075+
orig = raw.get_data(picks="eeg")
1076+
raw.rescale({"eeg": 2}) # need to use dict
1077+
assert_allclose(raw.get_data(picks="eeg"), orig * 2)
1078+
1079+
raw.pick("mag") # only a single channel type "mag"
1080+
orig = raw.get_data()
1081+
raw.rescale(4) # a scalar works
1082+
assert_allclose(raw.get_data(), orig * 4)

0 commit comments

Comments
 (0)