If you have a question or issue with MNE-Python, please include the following info:
- MNE version: mne (Python 3.11.7)
- operating system: Windows 10
I am trying to adapt a code used to analyse an experiment about motor cortex (tapping hands) to use it in an experiment about Prefrontal cortex (image visualization). Here is the already adapted code:
# %% [markdown]
#
#
# # Análise fNIRS
#
# Script para análise dos experimentos realizados no escopo do projeto Sonhos e regulação emocional.
#
#
# %%
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
from IPython.display import clear_output
# %%
from itertools import compress
import matplotlib.pyplot as plt
import numpy as np
import mne
# pasta onde estão armazenados os dados coletados nos experimentos
fnirs_data_folder = "C:/Users/CogLab/Documents/0_Exp_Brasil_Pasta/DATA/fNIRS Data/NIRSTAR Data"
# caminho completo do conjunto de dados a ser analisado: pasta do arquivo no sistema + nome da pasta do participante
fnirs_cw_amplitude_dir = fnirs_data_folder + "/2024-06-17_013_eu_fNIRS_isolado_Sem_cruz"
raw_intensity = mne.io.read_raw_nirx(fnirs_cw_amplitude_dir, verbose=True)
raw_intensity.load_data()
# %% [markdown]
# ## Providing more meaningful annotation information
#
# First, we attribute more meaningful names to the trigger codes which are
# stored as annotations. Second, we include information about the duration of
# each stimulus, which was 5 seconds for all conditions in this experiment.
# Third, we remove the trigger code 15, which signaled the start and end
# of the experiment and is not relevant to our analysis.
#
#
# %%
print(raw_intensity.annotations.description)
# %% [markdown]
# O parâmetro para a função set_durations é duração da imagem na tela - ou seja, a duração do estímulo.
# %%
#escolhemos 9 segundos como duração do estímulo (1s de estímulo antes da instrução, 2s de instrução, 6s de estímulo após desaparcimento da instrução)
raw_intensity.annotations.set_durations(9)
raw_intensity.annotations.rename(
{"2.0": "Negativo/Visualizar", "4.0": "Negativo/Reavaliar", "8.0": "Neutro"}
)
unwanted = np.nonzero(raw_intensity.annotations.description == "3.0")
raw_intensity.annotations.delete(unwanted)
unwanted = np.nonzero(raw_intensity.annotations.description == "6.0")
raw_intensity.annotations.delete(unwanted)
unwanted = np.nonzero(raw_intensity.annotations.description == "BAD_SATURATED")
raw_intensity.annotations.delete(unwanted)
# %%
print(raw_intensity.annotations.description)
# %% [markdown]
# ## Viewing location of sensors over brain surface
#
# Here we validate that the location of sources-detector pairs and channels
# are in the expected locations. Source-detector pairs are shown as lines
# between the optodes, channels (the mid point of source-detector pairs) are
# optionally shown as orange dots. Source are optionally shown as red dots and
# detectors as black.
#
#
# %%
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)
# %% [markdown]
# ## Selecting channels appropriate for detecting neural responses
#
# First we remove channels that are too close together (short channels) to
# detect a neural response (less than 1 cm distance between optodes).
# These short channels can be seen in the figure above.
# To achieve this we pick all the channels that are not considered to be short.
#
#
# %%
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
)
# %% [markdown]
# ## Converting from raw intensity to optical density
#
# The raw intensity values are then converted to optical density.
#
#
# %%
raw_od = mne.preprocessing.nirs.optical_density(raw_intensity)
raw_od.plot(n_channels=len(raw_od.ch_names), duration=500, show_scrollbars=False)
# %% [markdown]
# ## Evaluating the quality of the data
#
# At this stage we can quantify the quality of the coupling
# between the scalp and the optodes using the scalp coupling index. This
# method looks for the presence of a prominent synchronous signal in the
# frequency range of cardiac signals across both photodetected signals.
#
# In this example the data is clean and the coupling is good for all
# channels, so we will not mark any channels as bad based on the scalp
# coupling index.
#
#
# %%
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])
# %% [markdown]
# In this example we will mark all channels with a SCI less than 0.5 as bad
# (this dataset is quite clean, so no channels are marked as bad).
#
#
# %%
raw_od.info["bads"] = list(compress(raw_od.ch_names, sci < 0.5))
# %% [markdown]
# Now we're gonna plot the bad channels in two images: one is the signals, and the other is the channels represented on the head.
# %%
raw_od
raw_od.copy().pick(range(20)).plot(duration=300, n_channels=len(raw_od.ch_names), clipping=None)
plt.rcParams["figure.figsize"] = (6, 6) # (w, h)
raw_od.plot_sensors()
# %% [markdown]
# At this stage it is appropriate to inspect your data
# (for instructions on how to use the interactive data visualisation tool
# see `tut-visualize-raw`)
# to ensure that channels with poor scalp coupling have been removed.
# If your data contains lots of artifacts you may decide to apply
# artifact reduction techniques as described in `ex-fnirs-artifacts`.
#
#
# %% [markdown]
# ## Converting from optical density to haemoglobin
#
# Next we convert the optical density data to haemoglobin concentration using
# the modified Beer-Lambert law.
#
#
# %%
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)
# %% [markdown]
# ## Removing heart rate from signal
#
# The haemodynamic response has frequency content predominantly below 0.5 Hz.
# An increase in activity around 1 Hz can be seen in the data that is due to
# the person's heart beat and is unwanted. So we use a low pass filter to
# remove this. A high pass filter is also included to remove slow drifts
# in the data.
#
#
# %%
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")
# %% [markdown]
# ## Extract epochs
#
# Now that the signal has been converted to relative haemoglobin concentration,
# and the unwanted heart rate component has been removed, we can extract epochs
# related to each of the experimental conditions.
#
# First we extract the events of interest and visualise them to ensure they are
# correct.
#
#
# %%
events, event_dict = mne.events_from_annotations(raw_haemo)
fig = mne.viz.plot_events(events, event_id=event_dict, sfreq=raw_haemo.info["sfreq"])
# %% [markdown]
# Next we define the range of our epochs, the rejection criteria,
# baseline correction, and extract the epochs. We visualise the log of which
# epochs were dropped.
#
#
# %%
reject_criteria = dict(hbo=80e-6) #O valor original era reject criteria = dict(hbo=80e-6)
#-2: 2 segundos antes de aparecer a imagem
#9: 9 segundos após o evento
# 25: 9 segundos de imagem, 4 de tempo estimado do SAM, 12 de ITI = 25
tmin, tmax = -2, 9
epochs = mne.Epochs(
raw_haemo,
events,
event_id=event_dict,
tmin=tmin,
tmax=tmax,
reject=reject_criteria,
reject_by_annotation=True,
proj=True,
#baseline=(10, 40),
baseline=(-2, -1),
preload=True,
detrend=None,
verbose=True,
)
epochs.plot_drop_log()
# %% [markdown]
# ## View consistency of responses across trials
#
# Now we can view the haemodynamic response for our condition.
# We visualise the response for both the oxy- and deoxyhaemoglobin, and
# observe the expected peak in HbO at around 6 seconds consistently across
# trials, and the consistent dip in HbR that is slightly delayed relative to
# the HbO peak.
#
#
# %%
epochs["Negativo/Visualizar"].plot_image(
combine="mean",
vmin=-30,
vmax=30,
ts_args=dict(ylim=dict(hbo=[-15, 15], hbr=[-15, 15])),
)
# %%
epochs["Negativo/Reavaliar"].plot_image(
combine="mean",
vmin=-30,
vmax=30,
ts_args=dict(ylim=dict(hbo=[-15, 15], hbr=[-15, 15])),
)
# %% [markdown]
# We can also view the epoched data for the control condition and observe
# that it does not show the expected morphology.
#
#
# %%
epochs["Neutro"].plot_image(
combine="mean",
vmin=-30,
vmax=30,
ts_args=dict(ylim=dict(hbo=[-15, 15], hbr=[-15, 15])),
)
# %% [markdown]
# ## View consistency of responses across channels
#
# Similarly we can view how consistent the response is across the optode
# pairs that we selected. All the channels in this data are located over the
# prefrontal cortex, and all channels show a similar pattern in the data.
#
#
# %%
fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(20, 8), layout="constrained")
clims = dict(hbo=[-30, 30], hbr=[-30, 30])
# Plotagem das condições nos subplots
epochs["Negativo/Visualizar"].average().plot_image(axes=axes[0, :], clim=clims)
epochs["Negativo/Reavaliar"].average().plot_image(axes=axes[1, :], clim=clims)
epochs["Neutro"].average().plot_image(axes=axes[2, :], clim=clims)
# Definição dos títulos para cada gráfico
conditions = ["Negativo/Visualizar", "Negativo/Reavaliar", "Neutro"]
for row, condition in enumerate(conditions):
for ax in axes[row, :]:
old_title = ax.get_title()
title = "{}: {}".format(condition, old_title)
ax.set_title(title)
# Exibição do gráfico
clear_output()
display(fig)
# %% [markdown]
# ## Plot standard fNIRS response image
#
# Next we generate the most common visualisation of fNIRS data: plotting
# both the HbO and HbR on the same figure to illustrate the relation between
# the two signals.
#
#
# %%
import matplotlib.pyplot as plt
import mne
evoked_dict = {
"Neg_Vis/HbO": epochs["Negativo/Visualizar"].average(picks="hbo"),
"Neg_Vis/HbR": epochs["Negativo/Visualizar"].average(picks="hbr"),
"Neg_Rea/HbO": epochs["Negativo/Reavaliar"].average(picks="hbo"),
"Neg_Rea/HbR": epochs["Negativo/Reavaliar"].average(picks="hbr"),
"Neu/HbO": epochs["Neutro"].average(picks="hbo"),
"Neu/HbR": epochs["Neutro"].average(picks="hbr"),
}
# Rename channels until the encoding of frequency in ch_name is fixed
for condition in evoked_dict:
evoked_dict[condition].rename_channels(lambda x: x[:-4])
# Define cores para cada condição
color_dict = {
"Neg_Vis/HbO": "#8B0000", # Vermelho escuro
"Neg_Vis/HbR": "#FF6347", # Vermelho claro
"Neg_Rea/HbO": "#006400", # Verde escuro
"Neg_Rea/HbR": "#98FB98", # Verde claro
"Neu/HbO": "#00008B", # Azul escuro
"Neu/HbR": "#87CEFA" # Azul claro
}
# Plot compare evokeds
fig, ax = plt.subplots(figsize=(10, 6))
mne.viz.plot_compare_evokeds(
evoked_dict, combine="mean", ci=0.95, colors=color_dict, axes=ax, show=False
)
# Ajuste a legenda para ter 2 linhas e 3 colunas na parte superior da figura
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels, ncol=3, loc='upper center', bbox_to_anchor=(0.5, 1.15), fontsize='small')
plt.show()
# %% [markdown]
# ## View topographic representation of activity (Oxyhemoglobin)
#
# Next we view how the topographic activity changes throughout the response (Oxyhemoglobin only)
#
# %%
# Define times within the valid range of your data (Os números abaixo são, respectivamente, o tempo mínimo e o tempo máximo a ser plotado, além do intervalo para plotar mais uma cabeça)
times = np.arange(-2, 9, 2.0)
# Now, use these corrected times in your plotting function
topomap_args = dict(extrapolate="local")
epochs["Negativo/Visualizar"].average(picks="hbo").plot_joint(
times=times, topomap_args=topomap_args, title="Negativo/Visualizar_Hbo"
)
epochs["Negativo/Reavaliar"].average(picks="hbo").plot_joint(
times=times, topomap_args=topomap_args, title="Negativo/Reavaliar_Hbo"
)
epochs["Neutro"].average(picks="hbo").plot_joint(
times=times, topomap_args=topomap_args, title="Neutro_Hbo"
)
# %%
print(raw_intensity.info['chs'])
# %% [markdown]
# ## Compare tapping of left and right hands
#
# Finally we generate topo maps for the left and right conditions to view
# the location of activity. First we visualise the HbO activity.
#
#
# %%
times = np.arange(-2.0, 9.0, 1.0)
epochs["Negativo/Visualizar"].average(picks="hbo").plot_topomap(times=times, **topomap_args)
epochs["Negativo/Reavaliar"].average(picks="hbo").plot_topomap(times=times, **topomap_args)
epochs["Neutro"].average(picks="hbo").plot_topomap(times=times, **topomap_args)
# %% [markdown]
# And we also view the HbR activity for the two conditions.
#
#
# %%
epochs["Negativo/Visualizar"].average(picks="hbr").plot_topomap(times=times, **topomap_args)
epochs["Negativo/Reavaliar"].average(picks="hbr").plot_topomap(times=times, **topomap_args)
epochs["Neutro"].average(picks="hbr").plot_topomap(times=times, **topomap_args)
# %% [markdown]
# And we can plot the comparison at a single time point for two conditions.
#
#
# %%
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 = 6.0
evoked_left = epochs["Negativo/Visualizar"].average()
evoked_right = epochs["Negativo/Reavaliar"].average()
evoked_right = epochs["Neutro"].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(["Negativo_Visualizar", "Negativo_Reavaliar", "Visuazlizar-Reavaliar"]):
for row, chroma in enumerate(["HbO", "HbR"]):
axes[row, column].set_title("{}: {}".format(chroma, condition))
# %% [markdown]
# Lastly, we can also look at the individual waveforms to see what is
# driving the topographic plot above.
#
#
# %%
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(6, 4), layout="constrained")
mne.viz.plot_evoked_topo(
epochs["Visualizar"].average(picks="hbo"), color="b", axes=axes, legend=False
)
mne.viz.plot_evoked_topo(
epochs["Reavaliar"].average(picks="hbo"), color="r", axes=axes, legend=False
)
# Tidy the legend:
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, ["Visualizar", "Reavaliar"], loc="lower right")
Now i want to plot a comparision between two conditions. This is what i already adapted:
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 = 6.0
evoked_left = epochs["Negativo/Visualizar"].average()
evoked_right = epochs["Negativo/Reavaliar"].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(["Negativo_Visualizar", "Negativo_Reavaliar", "Visualizar-Reavaliar"]):
for row, chroma in enumerate(["HbO", "HbR"]):
axes[row, column].set_title("{}: {}".format(chroma, condition))
However, the functions “evoked_left”, “evoked_right” and “evoked diff” are too specific for the original experiment envolving the movement of two hands. So i don’t know how to adapt them. The image is not being plotted, because i am using functions which don’t fit to my experiment.