-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] add plot_anat_landmarks function #824
base: main
Are you sure you want to change the base?
Changes from 1 commit
94f1356
0593b58
bc728d9
8ebeadb
6dc7c45
ca17020
3e0deb0
2e7ee25
19afd3c
f69e55a
0c8f5d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,7 @@ | |
|
||
from mne_bids import (write_raw_bids, BIDSPath, write_anat, | ||
get_head_mri_trans, print_dir_tree) | ||
from mne_bids.viz import plot_anat_landmarks | ||
|
||
############################################################################### | ||
# We will be using the `MNE sample data <mne_sample_data_>`_ and write a basic | ||
|
@@ -134,16 +135,20 @@ | |
anat_dir = t1w_bids_path.directory | ||
|
||
############################################################################### | ||
# Let's have another look at our BIDS directory | ||
# Let's have another look at our BIDS directory and plot the written landmarks | ||
print_dir_tree(output_path) | ||
|
||
plot_anat_landmarks(t1w_bids_path, vmax=160) | ||
plt.suptitle('T1 MRI') | ||
|
||
############################################################################### | ||
# Our BIDS dataset is now ready to be shared. We can easily estimate the | ||
# transformation matrix using ``MNE-BIDS`` and the BIDS dataset. | ||
# transformation matrix using ``MNE-BIDS`` and the BIDS dataset since we | ||
# have now anatomical landmarks. | ||
estim_trans = get_head_mri_trans(bids_path=bids_path) | ||
|
||
############################################################################### | ||
# Finally, let's use the T1 weighted MRI image and plot the anatomical | ||
# One can also directly use the T1 weighted MRI image and plot the anatomical | ||
agramfort marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# landmarks Nasion, LPA, and RPA onto the brain image. For that, we can | ||
# extract the location of Nasion, LPA, and RPA from the MEG file, apply our | ||
# transformation matrix :code:`trans`, and plot the results. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wondering if we should start off this paragraph differently or put it into a new subsection. The first sentence made me think, "umm, but we just DID that??" until I got to the point where it says, " ... from the MEG file" |
||
|
@@ -169,7 +174,7 @@ | |
t1_nii_fname = op.join(anat_dir, 'sub-01_ses-01_T1w.nii.gz') | ||
|
||
# Plot it | ||
fig, axs = plt.subplots(3, 1, figsize=(7, 7), facecolor='k') | ||
fig, axs = plt.subplots(3, 1, figsize=(7, 7)) | ||
for point_idx, label in enumerate(('LPA', 'NAS', 'RPA')): | ||
plot_anat(t1_nii_fname, axes=axs[point_idx], | ||
cut_coords=mri_pos[point_idx, :], | ||
|
@@ -214,9 +219,8 @@ | |
t1_nii_fname = op.join(anat_dir, 'sub-01_ses-01_T1w.nii.gz') | ||
|
||
# Plot it | ||
fig, ax = plt.subplots() | ||
plot_anat(t1_nii_fname, axes=ax, title='Defaced', vmax=160) | ||
plt.show() | ||
plot_anat_landmarks(t1w_bids_path, vmax=160) | ||
plt.suptitle('T1 Defaced') | ||
|
||
############################################################################### | ||
# Writing defaced and anonymized FLASH MRI image | ||
|
@@ -250,9 +254,8 @@ | |
flash_nii_fname = op.join(anat_dir, 'sub-01_ses-01_FLASH.nii.gz') | ||
|
||
# Plot it | ||
fig, ax = plt.subplots() | ||
plot_anat(flash_nii_fname, axes=ax, title='Defaced', vmax=700) | ||
plt.show() | ||
plot_anat_landmarks(flash_bids_path, vmax=700) | ||
plt.suptitle('Flash Defaced') | ||
|
||
############################################################################### | ||
# Option 2 : Use manual landmarks coordinates in scanner RAS for FLASH image | ||
|
@@ -282,9 +285,8 @@ | |
) | ||
|
||
# Plot it | ||
fig, ax = plt.subplots() | ||
plot_anat(flash_nii_fname, axes=ax, title='Defaced', vmax=700) | ||
plt.show() | ||
plot_anat_landmarks(flash_bids_path, vmax=700) | ||
plt.suptitle('Flash Defaced') | ||
|
||
############################################################################### | ||
# Option 3 : Compute the landmarks in scanner RAS or mri voxel space from trans | ||
|
@@ -324,9 +326,8 @@ | |
) | ||
|
||
# Plot it | ||
fig, ax = plt.subplots() | ||
plot_anat(flash_nii_fname, axes=ax, title='Defaced', vmax=700) | ||
plt.show() | ||
plot_anat_landmarks(flash_bids_path, vmax=700) | ||
plt.suptitle('Flash Defaced') | ||
|
||
############################################################################## | ||
# Let's now pass it in voxel coordinates | ||
|
@@ -352,9 +353,8 @@ | |
) | ||
|
||
# Plot it | ||
fig, ax = plt.subplots() | ||
plot_anat(flash_nii_fname, axes=ax, title='Defaced', vmax=700) | ||
plt.show() | ||
plot_anat_landmarks(flash_bids_path, vmax=700) | ||
plt.suptitle('Flash Defaced') | ||
|
||
############################################################################### | ||
# .. LINKS | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from pathlib import Path | ||
|
||
import pytest | ||
|
||
import mne | ||
from mne.datasets import testing | ||
from mne_bids.viz import plot_anat_landmarks | ||
from mne_bids import BIDSPath, write_anat | ||
|
||
from mne.utils import requires_nibabel, requires_version | ||
agramfort marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@requires_nibabel() | ||
@requires_version('nilearn', '0.6') | ||
def test_plot_anat_landmarks(tmpdir): | ||
"""Test writing anatomical data with pathlib.Paths.""" | ||
data_path = Path(testing.data_path()) | ||
raw_fname = data_path / 'MEG' / 'sample' / 'sample_audvis_trunc_raw.fif' | ||
trans_fname = str(raw_fname).replace('_raw.fif', '-trans.fif') | ||
raw = mne.io.read_raw_fif(raw_fname) | ||
trans = mne.read_trans(trans_fname) | ||
|
||
bids_root = Path(tmpdir) | ||
t1w_mgh_fname = Path(data_path) / 'subjects' / 'sample' / 'mri' / 'T1.mgz' | ||
bids_path = BIDSPath(subject='01', session='mri', root=bids_root) | ||
bids_path = write_anat(t1w_mgh_fname, bids_path=bids_path, overwrite=True) | ||
|
||
with pytest.warns(RuntimeWarning, match='No landmarks available'): | ||
fig = plot_anat_landmarks(bids_path, show=False) | ||
assert fig is None | ||
|
||
bids_path = write_anat(t1w_mgh_fname, bids_path=bids_path, raw=raw, | ||
trans=trans, deface=True, verbose=True, | ||
overwrite=True) | ||
fig = plot_anat_landmarks(bids_path, show=False) | ||
assert len(fig.axes) == 12 # 3 subplots + 3 x 3 MRI slices |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import json | ||
|
||
import numpy as np | ||
from scipy import linalg | ||
|
||
import nibabel as nib | ||
from mne.utils import warn | ||
import mne | ||
agramfort marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def plot_anat_landmarks(bids_path, vmax=None, show=True): | ||
"""Plot anatomical landmarks attached to an MRI image. | ||
|
||
Parameters | ||
---------- | ||
bids_path : mne_bids.BIDSPath | ||
Path of the MRI image. | ||
vmax : float | ||
Maximum colormap value. | ||
Comment on lines
+16
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we limit the acceptable range, e.g. to the interval [0, 255]? |
||
show : bool | ||
Call pyplot.show() at the end. Defaults to True. | ||
agramfort marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Returns | ||
------- | ||
fig : matplotlib.figure.Figure | None | ||
The figure object containing the plot. None if no landmarks | ||
are avaiable. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we raise an exception if there are no landmarks? |
||
""" | ||
import matplotlib.pyplot as plt | ||
from nilearn.plotting import plot_anat | ||
|
||
nii = nib.load(str(bids_path)) | ||
|
||
json_path = bids_path.copy().update(extension=".json") | ||
|
||
n_landmarks = 0 | ||
if json_path.fpath.exists(): | ||
json_content = json.load(open(json_path)) | ||
coords_dict = json_content.get("AnatomicalLandmarkCoordinates", dict()) | ||
n_landmarks = len(coords_dict) | ||
|
||
if not n_landmarks: | ||
warn("No landmarks available with the image") | ||
return | ||
|
||
# Move the coords_dict from MRI Voxel to RAS | ||
mgh = nib.MGHImage(nii.dataobj, nii.affine) | ||
ras2vox = mgh.header.get_ras2vox() | ||
vox2ras = linalg.inv(ras2vox) | ||
for label in coords_dict: | ||
vox_pos = np.array(coords_dict[label]) | ||
ras_pos = mne.transforms.apply_trans(vox2ras, vox_pos) | ||
coords_dict[label] = ras_pos | ||
|
||
######################################################################## | ||
# Plot it with nilearn | ||
fig, axs = plt.subplots( | ||
n_landmarks, 1, figsize=(6, 2.3 * n_landmarks), | ||
facecolor="w") | ||
|
||
for point_idx, (label, ras_pos) in enumerate(coords_dict.items()): | ||
plot_anat( | ||
str(bids_path), axes=axs[point_idx], cut_coords=ras_pos, | ||
title=label, vmax=vmax, | ||
) | ||
|
||
if show: | ||
plt.show() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this better be |
||
|
||
return fig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be moved into
plot_anat_landmarks
? It would be nice if the figure came with a sensible title by defaultThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would also be good to briefly explain what
vmax
is doing here?