|
@@ -1,5 +1,13 @@
|
|
# Reading and processing NIRS data with MNE-Python
|
|
# Reading and processing NIRS data with MNE-Python
|
|
|
|
|
|
|
|
+This tutorial demonstrates how to read and process NIRS data using
|
|
|
|
+MNE-Python and `almirah`.
|
|
|
|
+
|
|
|
|
+## Setup
|
|
|
|
+
|
|
|
|
+First, we'll import the necessary libraries and set up the logging and
|
|
|
|
+warning configurations.
|
|
|
|
+
|
|
```python
|
|
```python
|
|
import mne
|
|
import mne
|
|
import warnings
|
|
import warnings
|
|
@@ -13,31 +21,53 @@ mne.set_log_level(False)
|
|
warnings.filterwarnings('ignore')
|
|
warnings.filterwarnings('ignore')
|
|
```
|
|
```
|
|
|
|
|
|
|
|
+## Loading the Data
|
|
|
|
+
|
|
|
|
+Next, we'll set up the layout to access the NIRS data.
|
|
|
|
+
|
|
```python
|
|
```python
|
|
lay = Layout(root="/path/to/data", specification_name="bids")
|
|
lay = Layout(root="/path/to/data", specification_name="bids")
|
|
print(lay)
|
|
print(lay)
|
|
```
|
|
```
|
|
|
|
|
|
|
|
+This should output:
|
|
|
|
+
|
|
<Layout root: '/path/to/data'>
|
|
<Layout root: '/path/to/data'>
|
|
|
|
|
|
|
|
+We can query the layout to find all NIRS files with the `.snirf` extension:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
files = lay.query(datatype="nirs", extension=".snirf")
|
|
files = lay.query(datatype="nirs", extension=".snirf")
|
|
len(files)
|
|
len(files)
|
|
```
|
|
```
|
|
|
|
|
|
|
|
+This gives the total number of NIRS files:
|
|
|
|
+
|
|
1315
|
|
1315
|
|
|
|
|
|
|
|
+## Querying a Specific File
|
|
|
|
+
|
|
|
|
+To query a specific file, we can filter by subject, task, datatype, and extension:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
file = lay.query(subject="D0019", task="rest", datatype="nirs", extension=".snirf")[0]
|
|
file = lay.query(subject="D0019", task="rest", datatype="nirs", extension=".snirf")[0]
|
|
print(file.rel_path)
|
|
print(file.rel_path)
|
|
```
|
|
```
|
|
|
|
|
|
|
|
+This should output the relative path of the file:
|
|
|
|
+
|
|
sub-D0019/ses-111/nirs/sub-D0019_ses-111_task-rest_run-01_nirs.snirf
|
|
sub-D0019/ses-111/nirs/sub-D0019_ses-111_task-rest_run-01_nirs.snirf
|
|
|
|
|
|
|
|
+We can then download the NIRS file:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
file.download()
|
|
file.download()
|
|
```
|
|
```
|
|
|
|
|
|
|
|
+## Reading Raw Data
|
|
|
|
+
|
|
|
|
+Next, we read the raw NIRS data:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
raw = mne.io.read_raw_snirf(file.path)
|
|
raw = mne.io.read_raw_snirf(file.path)
|
|
raw.load_data()
|
|
raw.load_data()
|
|
@@ -49,12 +79,10 @@ raw.load_data()
|
|
<tr>
|
|
<tr>
|
|
<th>Measurement date</th>
|
|
<th>Measurement date</th>
|
|
<td>November 12, 1917 00:00:00 GMT</td>
|
|
<td>November 12, 1917 00:00:00 GMT</td>
|
|
-
|
|
|
|
</tr>
|
|
</tr>
|
|
<tr>
|
|
<tr>
|
|
<th>Experimenter</th>
|
|
<th>Experimenter</th>
|
|
<td>Unknown</td>
|
|
<td>Unknown</td>
|
|
-
|
|
|
|
</tr>
|
|
</tr>
|
|
<tr>
|
|
<tr>
|
|
<th>Participant</th>
|
|
<th>Participant</th>
|
|
@@ -112,7 +140,7 @@ raw.load_data()
|
|
</tr>
|
|
</tr>
|
|
</table>
|
|
</table>
|
|
</details>
|
|
</details>
|
|
-
|
|
|
|
|
|
+
|
|
```python
|
|
```python
|
|
print(raw.info)
|
|
print(raw.info)
|
|
```
|
|
```
|
|
@@ -132,6 +160,11 @@ print(raw.info)
|
|
subject_info: 4 items (dict)
|
|
subject_info: 4 items (dict)
|
|
>
|
|
>
|
|
|
|
|
|
|
|
+## Preprocessing
|
|
|
|
+
|
|
|
|
+We pick the NIRS channels, compute the source-detector distances, and
|
|
|
|
+filter out channels with a distance greater than 0.01:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
picks = mne.pick_types(raw.info, meg=False, fnirs=True)
|
|
picks = mne.pick_types(raw.info, meg=False, fnirs=True)
|
|
dists = mne.preprocessing.nirs.source_detector_distances(raw.info, picks=picks)
|
|
dists = mne.preprocessing.nirs.source_detector_distances(raw.info, picks=picks)
|
|
@@ -142,6 +175,8 @@ plt.show()
|
|
|
|
|
|
![png](../images/nirs/raw-plot.png)
|
|
![png](../images/nirs/raw-plot.png)
|
|
|
|
|
|
|
|
+We convert the raw data to optical density:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
raw_od = mne.preprocessing.nirs.optical_density(raw)
|
|
raw_od = mne.preprocessing.nirs.optical_density(raw)
|
|
raw_od.plot(n_channels=len(raw_od.ch_names), duration=500, show_scrollbars=False)
|
|
raw_od.plot(n_channels=len(raw_od.ch_names), duration=500, show_scrollbars=False)
|
|
@@ -150,6 +185,8 @@ plt.show()
|
|
|
|
|
|
![png](../images/nirs/optical-density-plot.png)
|
|
![png](../images/nirs/optical-density-plot.png)
|
|
|
|
|
|
|
|
+Next, we compute the Scalp Coupling Index (SCI) and plot the distribution:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
sci = mne.preprocessing.nirs.scalp_coupling_index(raw_od)
|
|
sci = mne.preprocessing.nirs.scalp_coupling_index(raw_od)
|
|
fig, ax = plt.subplots()
|
|
fig, ax = plt.subplots()
|
|
@@ -160,10 +197,14 @@ plt.show()
|
|
|
|
|
|
![png](../images/nirs/scalp-coupling-index-plot.png)
|
|
![png](../images/nirs/scalp-coupling-index-plot.png)
|
|
|
|
|
|
|
|
+We mark channels with a low SCI as bad:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
raw_od.info["bads"] = list(compress(raw_od.ch_names, sci < 0.5))
|
|
raw_od.info["bads"] = list(compress(raw_od.ch_names, sci < 0.5))
|
|
```
|
|
```
|
|
|
|
|
|
|
|
+We convert the optical density data to hemoglobin concentration:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
raw_haemo = mne.preprocessing.nirs.beer_lambert_law(raw_od, ppf=0.1)
|
|
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.plot(n_channels=len(raw_haemo.ch_names), duration=500, show_scrollbars=False)
|
|
@@ -172,6 +213,10 @@ plt.show()
|
|
|
|
|
|
![png](../images/nirs/raw-haemo-plot.png)
|
|
![png](../images/nirs/raw-haemo-plot.png)
|
|
|
|
|
|
|
|
+## Power Spectral Analysis
|
|
|
|
+
|
|
|
|
+We compute and plot the Power Spectral Density (PSD) before and after filtering:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
fig = raw_haemo.compute_psd().plot(average=True, amplitude=False)
|
|
fig = raw_haemo.compute_psd().plot(average=True, amplitude=False)
|
|
fig.suptitle("Before filtering", weight="bold", size="x-large")
|
|
fig.suptitle("Before filtering", weight="bold", size="x-large")
|
|
@@ -185,10 +230,16 @@ plt.show()
|
|
|
|
|
|
![png](../images/nirs/raw-haemo-psd-after-filtering.png)
|
|
![png](../images/nirs/raw-haemo-psd-after-filtering.png)
|
|
|
|
|
|
|
|
+## Epoching
|
|
|
|
+
|
|
|
|
+We create epochs from the continuous data:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
epochs = mne.make_fixed_length_epochs(raw_haemo, duration=30, preload=False)
|
|
epochs = mne.make_fixed_length_epochs(raw_haemo, duration=30, preload=False)
|
|
```
|
|
```
|
|
|
|
|
|
|
|
+Finally, we plot the epochs:
|
|
|
|
+
|
|
```python
|
|
```python
|
|
epochs.plot_image(combine="mean", vmin=-30, vmax=30,
|
|
epochs.plot_image(combine="mean", vmin=-30, vmax=30,
|
|
ts_args=dict(ylim=dict(hbo=[-15, 15],
|
|
ts_args=dict(ylim=dict(hbo=[-15, 15],
|
|
@@ -199,4 +250,6 @@ plt.show()
|
|
![png](../images/nirs/deoxyhemoglobin-plot.png)
|
|
![png](../images/nirs/deoxyhemoglobin-plot.png)
|
|
|
|
|
|
![png](../images/nirs/oxyhemoglobin-plot.png)
|
|
![png](../images/nirs/oxyhemoglobin-plot.png)
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+This concludes the tutorial. You've learned how to read, and process
|
|
|
|
+near-infrared spectroscopy data using MNE-Python and `almirah`.
|