Storing estimated HRFs in snirf files
This notebook estimates the HRF in a finger-tapping experiment by blockaveraging and then stores the result in a snirf file.
[1]:
import cedalion
import cedalion.nirs
import cedalion.datasets
import cedalion.io
from snirf import Snirf
import numpy as np
import xarray as xr
import matplotlib.pyplot as p
import os.path
import tempfile
xr.set_options(display_max_rows=3, display_values_threshold=50, display_expand_data=False)
np.set_printoptions(precision=4)
Load a finger-tapping dataset
For this demo we load an example finger-tapping recording through cedalion.datasets.get_fingertapping
. The file contains a single NIRS element with one block of raw amplitude data.
[2]:
elements = cedalion.datasets.get_fingertapping()
[3]:
amp = elements[0].data[0]
stim = elements[0].stim # pandas Dataframe
geo3d = elements[0].geo3d
# Rename events
stim.cd.rename_events( {
"1.0" : "control",
"2.0" : "Tapping/Left",
"3.0" : "Tapping/Right"
})
Calculate concentrations
[4]:
dpf = xr.DataArray([6, 6], dims="wavelength", coords={"wavelength" : amp.wavelength})
od = - np.log( amp / amp.mean("time") )
conc = cedalion.nirs.beer_lambert(amp, geo3d, dpf)
Frequency filtering and splitting into epochs
[5]:
conc_freqfilt = conc.cd.freq_filter(fmin=0.02, fmax=0.5, butter_order=4)
cf_epochs = conc_freqfilt.cd.to_epochs(
stim, # stimulus dataframe
["Tapping/Left", "Tapping/Right"], # select events
before=5, # seconds before stimulus
after=20 # seconds after stimulus
)
Blockaveraging to estimate the HRFs
[6]:
# calculate baseline
baseline = cf_epochs.sel(reltime=(cf_epochs.reltime < 0)).mean("reltime")
# subtract baseline
epochs_blcorrected = cf_epochs - baseline
# group trials by trial_type. For each group individually average the epoch dimension
blockaverage = epochs_blcorrected.groupby("trial_type").mean("epoch")
display(blockaverage)
<xarray.DataArray 'concentration' (trial_type: 2, chromo: 2, channel: 28, reltime: 196)> Size: 176kB [µM] -0.05399 -0.05393 -0.05398 -0.05408 ... -0.005362 -0.005991 -0.007054 Coordinates: (3/6) * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 224B 'S1D1' 'S1D2' 'S1D3' ... 'S8D8' 'S8D16' ... ... * trial_type (trial_type) object 16B 'Tapping/Left' 'Tapping/Right'
Store HRFs
[7]:
tmpdir = tempfile.TemporaryDirectory()
snirf_fname = os.path.join(tmpdir.name, "test.snirf")
[8]:
cedalion.io.snirf.write_snirf(
snirf_fname,
"hrf",
blockaverage,
geo3d,
stim
)
Inspect snirf file
[9]:
s = Snirf(snirf_fname)
Stim DataFrame
[10]:
cedalion.io.snirf.stim_to_dataframe(s.nirs[0].stim)
[10]:
onset | duration | value | trial_type | |
---|---|---|---|---|
0 | 61.824 | 5.0 | 1.0 | control |
1 | 87.296 | 5.0 | 1.0 | control |
2 | 181.504 | 5.0 | 1.0 | control |
3 | 275.712 | 5.0 | 1.0 | control |
4 | 544.640 | 5.0 | 1.0 | control |
... | ... | ... | ... | ... |
87 | 2234.112 | 5.0 | 1.0 | Tapping/Right |
88 | 2334.848 | 5.0 | 1.0 | Tapping/Right |
89 | 2362.240 | 5.0 | 1.0 | Tapping/Right |
90 | 2677.504 | 5.0 | 1.0 | Tapping/Right |
91 | 2908.032 | 5.0 | 1.0 | Tapping/Right |
92 rows × 4 columns
MeasurementList DataFrame
[11]:
df_ml = cedalion.io.snirf.measurement_list_to_dataframe(s.nirs[0].data[0].measurementList, drop_none=True)
df_ml
[11]:
sourceIndex | detectorIndex | dataType | dataUnit | dataTypeLabel | dataTypeIndex | |
---|---|---|---|---|---|---|
0 | 1 | 1 | 99999 | micromolar | HRF HbO | 3 |
1 | 1 | 1 | 99999 | micromolar | HRF HbR | 3 |
2 | 1 | 9 | 99999 | micromolar | HRF HbO | 3 |
3 | 1 | 9 | 99999 | micromolar | HRF HbR | 3 |
4 | 1 | 10 | 99999 | micromolar | HRF HbO | 3 |
... | ... | ... | ... | ... | ... | ... |
107 | 8 | 14 | 99999 | micromolar | HRF HbR | 4 |
108 | 8 | 15 | 99999 | micromolar | HRF HbO | 4 |
109 | 8 | 15 | 99999 | micromolar | HRF HbR | 4 |
110 | 8 | 8 | 99999 | micromolar | HRF HbO | 4 |
111 | 8 | 8 | 99999 | micromolar | HRF HbR | 4 |
112 rows × 6 columns
[12]:
s.close()
Read HRFs from snirf file
Note: read_snirf names the time dimension time
whereas in blockaverage
it was called reltime
. Need to agree on a convention.
[13]:
hrf_elements = cedalion.io.read_snirf(snirf_fname)
[14]:
read_blockaverage = hrf_elements[0].data[0]
display(read_blockaverage)
<xarray.DataArray (channel: 28, chromo: 2, trial_type: 2, time: 196)> Size: 176kB [µM] -0.05399 -0.05393 -0.05398 -0.05408 ... -0.005362 -0.005991 -0.007054 Coordinates: (3/7) * time (time) float64 2kB -4.992 -4.864 -4.736 ... 19.71 19.84 19.97 samples (time) int64 2kB 0 1 2 3 4 5 6 7 ... 189 190 191 192 193 194 195 ... ... * trial_type (trial_type) <U13 104B 'Tapping/Left' 'Tapping/Right' Attributes: data_type_group: processed HRF concentrations
Assert that the written and read HRFs are identical.
[15]:
assert (read_blockaverage.rename({"time" : "reltime"}) == blockaverage).all()
Plot the HRFs
[16]:
ba = read_blockaverage
f,ax = p.subplots(5,6, figsize=(12,10))
ax = ax.flatten()
for i_ch, ch in enumerate(ba.channel.values):
for ls, trial_type in zip(["-", "--"], ba.trial_type):
ax[i_ch].plot(ba.time, ba.sel(chromo="HbO", trial_type=trial_type, channel=ch), "r", lw=2, ls=ls)
ax[i_ch].plot(ba.time, ba.sel(chromo="HbR", trial_type=trial_type, channel=ch), "b", lw=2, ls=ls)
ax[i_ch].grid(1)
ax[i_ch].set_title(ch)
ax[i_ch].set_ylim(-.3, .6)
p.tight_layout()
![../_images/examples_store_hrfs_in_snirf_file_27_0.png](../_images/examples_store_hrfs_in_snirf_file_27_0.png)
Tidy up
[17]:
del tmpdir