"""
.. _tut-fnirs-processing:
================================================================
Preprocessing functional near-infrared spectroscopy (fNIRS) data
================================================================
This tutorial covers how to convert functional near-infrared spectroscopy (fNIRS) data
from raw measurements to relative oxyhaemoglobin (HbO) and deoxyhaemoglobin (HbR)
concentration, view the average waveform, and topographic representation of the
response.
Here we will work with the :ref:`fNIRS motor data <fnirs-motor-dataset>`.
"""
import matplotlib.pyplot as plt
import numpy as np
import mne
fnirs_data_folder = mne.datasets.fnirs_motor.data_path()
fnirs_cw_amplitude_dir = fnirs_data_folder / "Participant-1"
raw_intensity = mne.io.read_raw_nirx(fnirs_cw_amplitude_dir, verbose=True)
raw_intensity.load_data()
raw_intensity.annotations.set_durations(5)
raw_intensity.annotations.rename(
{"1.0": "Control", "2.0": "Tapping/Left", "3.0": "Tapping/Right"}
)
unwanted = np.nonzero(raw_intensity.annotations.description == "15.0")
raw_intensity.annotations.delete(unwanted)
subjects_dir = mne.datasets.sample.data_path() / "subjects"
brain = mne.viz.Brain(
"fsaverage", subjects_dir=subjects_dir, background="w", cortex="0.5"
)
brain.add_sensors(
raw_intensity.info,
trans="fsaverage",
fnirs=["channels", "pairs", "sources", "detectors"],
)
brain.show_view(azimuth=20, elevation=60, distance=400)
picks = mne.pick_types(raw_intensity.info, meg=False, fnirs=True)
dists = mne.preprocessing.nirs.source_detector_distances(
raw_intensity.info, picks=picks
)
raw_intensity.pick(picks[dists > 0.01])
raw_intensity.plot(
n_channels=len(raw_intensity.ch_names), duration=500, show_scrollbars=False
)
raw_od = mne.preprocessing.nirs.optical_density(raw_intensity)
raw_od.plot(n_channels=len(raw_od.ch_names), duration=500, show_scrollbars=False)
sci = mne.preprocessing.nirs.scalp_coupling_index(raw_od)
fig, ax = plt.subplots(layout="constrained")
ax.hist(sci)
ax.set(xlabel="Scalp Coupling Index", ylabel="Count", xlim=[0, 1])
raw_od.info["bads"] = [raw_od.ch_names[idx] for idx in np.where(sci < 0.5)[0]]
raw_haemo = mne.preprocessing.nirs.beer_lambert_law(raw_od, ppf=0.1)
raw_haemo.plot(n_channels=len(raw_haemo.ch_names), duration=500, show_scrollbars=False)
raw_haemo_unfiltered = raw_haemo.copy()
raw_haemo.filter(0.05, 0.7, h_trans_bandwidth=0.2, l_trans_bandwidth=0.02)
for when, _raw in dict(Before=raw_haemo_unfiltered, After=raw_haemo).items():
fig = _raw.compute_psd().plot(
average=True, amplitude=False, picks="data", exclude="bads"
)
fig.suptitle(f"{when} filtering", weight="bold", size="x-large")
events, event_dict = mne.events_from_annotations(raw_haemo)
fig = mne.viz.plot_events(events, event_id=event_dict, sfreq=raw_haemo.info["sfreq"])
reject_criteria = dict(hbo=80e-6)
tmin, tmax = -5, 15
epochs = mne.Epochs(
raw_haemo,
events,
event_id=event_dict,
tmin=tmin,
tmax=tmax,
reject=reject_criteria,
reject_by_annotation=True,
proj=True,
baseline=(None, 0),
preload=True,
detrend=None,
verbose=True,
)
epochs.plot_drop_log()
epochs["Tapping"].plot_image(
combine="mean",
vmin=-30,
vmax=30,
ts_args=dict(ylim=dict(hbo=[-15, 15], hbr=[-15, 15])),
)
epochs["Control"].plot_image(
combine="mean",
vmin=-30,
vmax=30,
ts_args=dict(ylim=dict(hbo=[-15, 15], hbr=[-15, 15])),
)
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(15, 6), layout="constrained")
clims = dict(hbo=[-20, 20], hbr=[-20, 20])
epochs["Control"].average().plot_image(axes=axes[:, 0], clim=clims)
epochs["Tapping"].average().plot_image(axes=axes[:, 1], clim=clims)
for column, condition in enumerate(["Control", "Tapping"]):
for ax in axes[:, column]:
ax.set_title(f"{condition}: {ax.get_title()}")
evoked_dict = {
"Tapping/HbO": epochs["Tapping"].average(picks="hbo"),
"Tapping/HbR": epochs["Tapping"].average(picks="hbr"),
"Control/HbO": epochs["Control"].average(picks="hbo"),
"Control/HbR": epochs["Control"].average(picks="hbr"),
}
for condition in evoked_dict:
evoked_dict[condition].rename_channels(lambda x: x[:-4])
color_dict = dict(HbO="#AA3377", HbR="b")
styles_dict = dict(Control=dict(linestyle="dashed"))
mne.viz.plot_compare_evokeds(
evoked_dict, combine="mean", ci=0.95, colors=color_dict, styles=styles_dict
)
times = np.arange(-3.5, 13.2, 3.0)
topomap_args = dict(extrapolate="local")
epochs["Tapping"].average(picks="hbo").plot_joint(
times=times, topomap_args=topomap_args
)
times = np.arange(4.0, 11.0, 1.0)
epochs["Tapping/Left"].average(picks="hbo").plot_topomap(times=times, **topomap_args)
epochs["Tapping/Right"].average(picks="hbo").plot_topomap(times=times, **topomap_args)
epochs["Tapping/Left"].average(picks="hbr").plot_topomap(times=times, **topomap_args)
epochs["Tapping/Right"].average(picks="hbr").plot_topomap(times=times, **topomap_args)
fig, axes = plt.subplots(
nrows=2,
ncols=4,
figsize=(9, 5),
gridspec_kw=dict(width_ratios=[1, 1, 1, 0.1]),
layout="constrained",
)
vlim = (-8, 8)
ts = 9.0
evoked_left = epochs["Tapping/Left"].average()
evoked_right = epochs["Tapping/Right"].average()
evoked_left.plot_topomap(
ch_type="hbo", times=ts, axes=axes[0, 0], vlim=vlim, colorbar=False, **topomap_args
)
evoked_left.plot_topomap(
ch_type="hbr", times=ts, axes=axes[1, 0], vlim=vlim, colorbar=False, **topomap_args
)
evoked_right.plot_topomap(
ch_type="hbo", times=ts, axes=axes[0, 1], vlim=vlim, colorbar=False, **topomap_args
)
evoked_right.plot_topomap(
ch_type="hbr", times=ts, axes=axes[1, 1], vlim=vlim, colorbar=False, **topomap_args
)
evoked_diff = mne.combine_evoked([evoked_left, evoked_right], weights=[1, -1])
evoked_diff.plot_topomap(
ch_type="hbo", times=ts, axes=axes[0, 2:], vlim=vlim, colorbar=True, **topomap_args
)
evoked_diff.plot_topomap(
ch_type="hbr", times=ts, axes=axes[1, 2:], vlim=vlim, colorbar=True, **topomap_args
)
for column, condition in enumerate(["Tapping Left", "Tapping Right", "Left-Right"]):
for row, chroma in enumerate(["HbO", "HbR"]):
axes[row, column].set_title(f"{chroma}: {condition}")
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(6, 4), layout="constrained")
mne.viz.plot_evoked_topo(
epochs["Left"].average(picks="hbo"), color="b", axes=axes, legend=False
)
mne.viz.plot_evoked_topo(
epochs["Right"].average(picks="hbo"), color="r", axes=axes, legend=False
)
leg_lines = [line for line in axes.lines if line.get_c() == "b"][:1]
leg_lines.append([line for line in axes.lines if line.get_c() == "r"][0])
fig.legend(leg_lines, ["Left", "Right"], loc="lower right")