Skip to content

Commit

Permalink
[MRG] [ENH] Radiological view for cross-sectional MRI plotting (nilea…
Browse files Browse the repository at this point in the history
…rn#3172)

* radiological view

* add plotting radiological example

* Update whats_new.rst

* Update nilearn/plotting/displays/_axes.py

Co-authored-by: Gensollen <[email protected]>

* Update examples/01_plotting/plot_demo_plotting.py

Co-authored-by: Gensollen <[email protected]>

* Update nilearn/plotting/img_plotting.py

Co-authored-by: Gensollen <[email protected]>

* Update nilearn/plotting/img_plotting.py

Co-authored-by: Gensollen <[email protected]>

* Update nilearn/plotting/img_plotting.py

Co-authored-by: Gensollen <[email protected]>

* Update nilearn/plotting/displays/_axes.py

Co-authored-by: Gensollen <[email protected]>

* line removed

* indent corrected

* update latest.rst

* Update whats_new.rst

* Update nilearn/plotting/displays/_axes.py

Co-authored-by: bthirion <[email protected]>

* added spaces

* update spacing

* update formatting

* added name

* formatting

* formatting

* formatting fixes

* Update _axes.py

* Update docs.py

* doc string shortcut

* Update nilearn/_utils/docs.py

Co-authored-by: Gensollen <[email protected]>

* Update nilearn/plotting/img_plotting.py

Co-authored-by: Gensollen <[email protected]>

* Update img_plotting.py

* Update docs.py

* update docs

* Fix merge of docs.py

* Remove extra default statement

* Resolve formatting issues

* Add radiological argument to GlassBrainAxes

* Add smoke tests for relevant plotting funcs

* Remove unneeded argument

* Improve testing

* Update whatsnew

* Change test data

---------

Co-authored-by: Gensollen <[email protected]>
Co-authored-by: bthirion <[email protected]>
Co-authored-by: ymzayek <[email protected]>
  • Loading branch information
4 people authored Aug 3, 2023
1 parent a95149b commit 99e8530
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ authors:
- given-names: Koen
family-names: Helwegen
website: https://github.com/koenhelwegen
- given-names: Konrad
family-names: Wagstyl
website: https://github.com/kwagstyl
- given-names: Konstantin
family-names: Shmelkov
website: https://github.com/kshmelkov
Expand Down
2 changes: 2 additions & 0 deletions doc/changes/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
NEW
---

- Volume plotting functions like :func:`~plotting.plot_img` now have an optional ``radiological`` parameter, defaulting to ``False``. If ``True``, this will invert the x-axis and ``L`` and ``R`` annotations to confirm to radiological conventional view. (:gh:`3172` by `Konrad Wagstyl`_ and `Yasmin Mzayek`_).

Fixes
-----
- Fix bug in ``nilearn.plotting.surf_plotting._plot_surf_matplotlib`` that would make vertices transparent when saving in PDF or SVG format (:gh:`3860` by `Mathieu Dugré`_).
Expand Down
8 changes: 8 additions & 0 deletions examples/01_plotting/plot_demo_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
threshold=3, title="plot_stat_map",
cut_coords=[36, -27, 66])


###############################################################################
# Making interactive visualizations with function `view_img`
# ----------------------------------------------------------
Expand Down Expand Up @@ -106,6 +107,13 @@
# Visualizing mean image (3D)
plotting.plot_epi(mean_haxby_img, title="plot_epi")

# It's also possible to visualize volumes in a LR-flipped "radiological" view
# Just set radiological=True
plotting.plot_stat_map(stat_img,
threshold=3, title="plot_stat_map",
cut_coords=[36, -27, 66],
radiological=True)

###############################################################################
# A call to plotting.show is needed to display the plots when running
# in script mode (ie outside IPython)
Expand Down
8 changes: 8 additions & 0 deletions nilearn/_utils/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,14 @@ def custom_function(vertices):
and the display is closed.
"""

# radiological
docdict['radiological'] = """
radiological : :obj:`bool`, default=False
Invert x axis and R L labels to plot sections as a radiological view.
If False (default), the left hemisphere is on the left of a coronal image.
If True, left hemisphere is on the right.
"""

# random_state
docdict['random_state'] = """
random_state : :obj:`int` or RandomState, optional
Expand Down
19 changes: 14 additions & 5 deletions nilearn/plotting/displays/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ class BaseAxes:
coord : :obj:`float`
The coordinate along the direction of the cut.
%(radiological)s
"""

def __init__(self, ax, direction, coord):
def __init__(self, ax, direction, coord, radiological=False):
self.ax = ax
self.direction = direction
self.coord = coord
self._object_bounds = list()
self.shape = None
self.radiological = radiological

def transform_to_2d(self, data, affine):
"""Transform to a 2D."""
Expand Down Expand Up @@ -109,7 +111,13 @@ def draw_left_right(self, size, bg_color, **kwargs):
if self.direction in 'xlr':
return
ax = self.ax
ax.text(.1, .95, 'L',
annotation_on_left = "L"
annotation_on_right = "R"
if self.radiological:
ax.invert_xaxis()
annotation_on_left = "R"
annotation_on_right = "L"
ax.text(.1, .95, annotation_on_left,
transform=ax.transAxes,
horizontalalignment='left',
verticalalignment='top',
Expand All @@ -118,7 +126,7 @@ def draw_left_right(self, size, bg_color, **kwargs):
ec=bg_color, fc=bg_color, alpha=1),
**kwargs)

ax.text(.9, .95, 'R',
ax.text(.9, .95, annotation_on_right,
transform=ax.transAxes,
horizontalalignment='right',
verticalalignment='top',
Expand Down Expand Up @@ -349,8 +357,9 @@ class GlassBrainAxes(BaseAxes):
"""

def __init__(self, ax, direction, coord, plot_abs=True, **kwargs):
super().__init__(ax, direction, coord)
def __init__(self, ax, direction, coord, plot_abs=True,
radiological=False, **kwargs):
super().__init__(ax, direction, coord, radiological=radiological)
self._plot_abs = plot_abs
if ax is not None:
object_bounds = plot_brain_schematics(ax, direction, **kwargs)
Expand Down
2 changes: 1 addition & 1 deletion nilearn/plotting/glass_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def _get_object_bounds(json_content, transform):


def plot_brain_schematics(ax, direction, **kwargs):
"""Create matplotlib patches from a json custom format and plots them \
"""Create matplotlib patches from a json custom format and plot them \
on a matplotlib Axes.
Parameters
Expand Down
59 changes: 40 additions & 19 deletions nilearn/plotting/img_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def _plot_img_with_bg(img, bg_img=None, cut_coords=None,
cbar_tick_format="%.2g",
brain_color=(0.5, 0.5, 0.5),
decimals=False,
radiological=False,
**kwargs):
"""Refer to the docstring of plot_img for parameters not listed below.
Expand Down Expand Up @@ -193,6 +194,7 @@ def _plot_img_with_bg(img, bg_img=None, cut_coords=None,
black_bg=black_bg,
colorbar=colorbar,
brain_color=brain_color,
radiological=radiological,
)
if bg_img is not None:
bg_img = _utils.check_niimg_3d(bg_img)
Expand Down Expand Up @@ -227,7 +229,7 @@ def plot_img(img, cut_coords=None, output_file=None, display_mode='ortho',
annotate=True, draw_cross=True, black_bg=False, colorbar=False,
cbar_tick_format="%.2g",
resampling_interpolation='continuous',
bg_img=None, vmin=None, vmax=None, **kwargs):
bg_img=None, vmin=None, vmax=None, radiological=False, **kwargs):
"""Plot cuts of a given image.
By default Frontal, Axial, and Lateral.
Expand Down Expand Up @@ -263,6 +265,7 @@ def plot_img(img, cut_coords=None, output_file=None, display_mode='ortho',
Default=None.
%(vmin)s
%(vmax)s
%(radiological)s
kwargs : extra keyword arguments, optional
Extra keyword arguments passed to matplotlib.pyplot.imshow.
Expand All @@ -276,7 +279,8 @@ def plot_img(img, cut_coords=None, output_file=None, display_mode='ortho',
resampling_interpolation=resampling_interpolation,
black_bg=black_bg, colorbar=colorbar,
cbar_tick_format=cbar_tick_format,
bg_img=bg_img, vmin=vmin, vmax=vmax, **kwargs)
bg_img=bg_img, vmin=vmin, vmax=vmax, radiological=radiological,
**kwargs)

return display

Expand Down Expand Up @@ -418,8 +422,8 @@ def plot_anat(anat_img=MNI152TEMPLATE, cut_coords=None,
output_file=None, display_mode='ortho', figure=None,
axes=None, title=None, annotate=True, threshold=None,
draw_cross=True, black_bg='auto', dim='auto', cmap=plt.cm.gray,
colorbar=False, cbar_tick_format="%.2g", vmin=None,
vmax=None, **kwargs):
colorbar=False, cbar_tick_format="%.2g", radiological=False,
vmin=None, vmax=None, **kwargs):
"""Plot cuts of an anatomical image.
By default 3 cuts: Frontal, Axial, and Lateral.
Expand Down Expand Up @@ -453,6 +457,7 @@ def plot_anat(anat_img=MNI152TEMPLATE, cut_coords=None,
Controls how to format the tick labels of the colorbar.
Ex: use "%%i" to display as integers.
Default is '%%.2g' for scientific notation.
%(radiological)s
%(vmin)s
%(vmax)s
Expand All @@ -479,7 +484,8 @@ def plot_anat(anat_img=MNI152TEMPLATE, cut_coords=None,
threshold=threshold, annotate=annotate,
draw_cross=draw_cross, black_bg=black_bg,
colorbar=colorbar, cbar_tick_format=cbar_tick_format,
vmin=vmin, vmax=vmax, cmap=cmap, **kwargs)
vmin=vmin, vmax=vmax, cmap=cmap,
radiological=radiological, **kwargs)
return display


Expand All @@ -488,7 +494,8 @@ def plot_epi(epi_img=None, cut_coords=None, output_file=None,
display_mode='ortho', figure=None, axes=None, title=None,
annotate=True, draw_cross=True, black_bg=True,
colorbar=False, cbar_tick_format="%.2g",
cmap=plt.cm.nipy_spectral, vmin=None, vmax=None, **kwargs):
cmap=plt.cm.nipy_spectral, vmin=None, vmax=None,
radiological=False, **kwargs):
"""Plot cuts of an EPI image.
By default 3 cuts: Frontal, Axial, and Lateral.
Expand Down Expand Up @@ -518,6 +525,7 @@ def plot_epi(epi_img=None, cut_coords=None, output_file=None,
Default=`plt.cm.nipy_spectral`.
%(vmin)s
%(vmax)s
%(radiological)s
Notes
-----
Expand All @@ -530,7 +538,8 @@ def plot_epi(epi_img=None, cut_coords=None, output_file=None,
threshold=None, annotate=annotate,
draw_cross=draw_cross, black_bg=black_bg,
colorbar=colorbar, cbar_tick_format=cbar_tick_format,
cmap=cmap, vmin=vmin, vmax=vmax, **kwargs)
cmap=cmap, vmin=vmin, vmax=vmax,
radiological=radiological, **kwargs)
return display


Expand Down Expand Up @@ -588,7 +597,7 @@ def plot_roi(roi_img, bg_img=MNI152TEMPLATE, cut_coords=None,
threshold=0.5, alpha=0.7, cmap=plt.cm.gist_ncar, dim='auto',
colorbar=False, cbar_tick_format="%i", vmin=None, vmax=None,
resampling_interpolation='nearest', view_type='continuous',
linewidths=2.5, **kwargs):
linewidths=2.5, radiological=False, **kwargs):
"""Plot cuts of an ROI/mask image.
By default 3 cuts: Frontal, Axial, and Lateral.
Expand Down Expand Up @@ -642,6 +651,7 @@ def plot_roi(roi_img, bg_img=MNI152TEMPLATE, cut_coords=None,
Default='continuous'.
%(linewidths)s
Default=2.5.
%(radiological)s
Notes
-----
Expand Down Expand Up @@ -677,7 +687,8 @@ def plot_roi(roi_img, bg_img=MNI152TEMPLATE, cut_coords=None,
threshold=threshold, bg_vmin=bg_vmin, bg_vmax=bg_vmax,
resampling_interpolation=resampling_interpolation,
colorbar=colorbar, cbar_tick_format=cbar_tick_format,
alpha=alpha, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs)
alpha=alpha, cmap=cmap, vmin=vmin, vmax=vmax,
radiological=radiological, **kwargs)

if view_type == 'contours':
display = _plot_roi_contours(display, img, cmap=cmap, alpha=alpha,
Expand All @@ -694,7 +705,7 @@ def plot_prob_atlas(maps_img, bg_img=MNI152TEMPLATE, view_type='auto',
draw_cross=True, black_bg='auto', dim='auto',
colorbar=False,
cmap=plt.cm.gist_rainbow, vmin=None, vmax=None,
alpha=0.7, **kwargs):
alpha=0.7, radiological=False, **kwargs):
"""Plot the probabilistic atlases onto the anatomical image \
by default MNI template.
Expand Down Expand Up @@ -762,6 +773,7 @@ def plot_prob_atlas(maps_img, bg_img=MNI152TEMPLATE, view_type='auto',
alpha : float between 0 and 1, optional
Alpha sets the transparency of the color inside the filled contours.
Default=0.7.
%(radiological)s
See Also
--------
Expand All @@ -772,7 +784,8 @@ def plot_prob_atlas(maps_img, bg_img=MNI152TEMPLATE, view_type='auto',
display_mode=display_mode,
figure=figure, axes=axes, title=title,
annotate=annotate, draw_cross=draw_cross,
black_bg=black_bg, dim=dim, **kwargs)
black_bg=black_bg, dim=dim, radiological=radiological,
**kwargs)

maps_img = _utils.check_niimg_4d(maps_img)
n_maps = maps_img.shape[3]
Expand Down Expand Up @@ -871,7 +884,7 @@ def plot_stat_map(stat_map_img, bg_img=MNI152TEMPLATE, cut_coords=None,
title=None, threshold=1e-6, annotate=True, draw_cross=True,
black_bg='auto', cmap=cm.cold_hot, symmetric_cbar="auto",
dim='auto', vmax=None, resampling_interpolation='continuous',
**kwargs):
radiological=False, **kwargs):
"""Plot cuts of an ROI/mask image.
By default 3 cuts: Frontal, Axial, and Lateral.
Expand Down Expand Up @@ -916,6 +929,7 @@ def plot_stat_map(stat_map_img, bg_img=MNI152TEMPLATE, cut_coords=None,
%(vmax)s
%(resampling_interpolation)s
Default='continuous'.
%(radiological)s
Notes
-----
Expand Down Expand Up @@ -951,7 +965,8 @@ def plot_stat_map(stat_map_img, bg_img=MNI152TEMPLATE, cut_coords=None,
bg_vmin=bg_vmin, bg_vmax=bg_vmax, cmap=cmap, vmin=vmin, vmax=vmax,
colorbar=colorbar, cbar_tick_format=cbar_tick_format,
cbar_vmin=cbar_vmin, cbar_vmax=cbar_vmax,
resampling_interpolation=resampling_interpolation, **kwargs)
resampling_interpolation=resampling_interpolation,
radiological=radiological, **kwargs)

return display

Expand All @@ -969,6 +984,7 @@ def plot_glass_brain(stat_map_img,
plot_abs=True,
symmetric_cbar="auto",
resampling_interpolation='continuous',
radiological=False,
**kwargs):
"""Plot 2d projections of an ROI/mask image (by default 3 projections:
Frontal, Axial, and Lateral). The brain glass schematics
Expand Down Expand Up @@ -1025,7 +1041,8 @@ def plot_glass_brain(stat_map_img,
Default='auto'.
%(resampling_interpolation)s
Default='continuous'.
%(radiological)s
Notes
-----
Arrays should be passed in numpy convention: (x, y, z) ordered.
Expand Down Expand Up @@ -1062,7 +1079,8 @@ def display_factory(display_mode):
black_bg=black_bg, threshold=threshold, cmap=cmap, colorbar=colorbar,
cbar_tick_format=cbar_tick_format, display_factory=display_factory,
vmin=vmin, vmax=vmax, cbar_vmin=cbar_vmin, cbar_vmax=cbar_vmax,
resampling_interpolation=resampling_interpolation, **kwargs)
resampling_interpolation=resampling_interpolation,
radiological=radiological, **kwargs)

if stat_map_img is None and 'l' in display.axes:
display.axes['l'].ax.invert_xaxis()
Expand All @@ -1081,7 +1099,7 @@ def plot_connectome(adjacency_matrix, node_coords,
annotate=True, black_bg=False,
alpha=0.7,
edge_kwargs=None, node_kwargs=None,
colorbar=False):
colorbar=False, radiological=False):
"""Plot connectome on top of the brain glass schematics.
The plotted image should be in MNI space for this function to work
Expand Down Expand Up @@ -1149,6 +1167,7 @@ def plot_connectome(adjacency_matrix, node_coords,
the nodes in one go.
%(colorbar)s
Default=False.
%(radiological)s
See Also
--------
Expand All @@ -1163,7 +1182,7 @@ def plot_connectome(adjacency_matrix, node_coords,
figure=figure, axes=axes, title=title,
annotate=annotate,
black_bg=black_bg,
alpha=alpha)
alpha=alpha, radiological=radiological)

display.add_graph(adjacency_matrix, node_coords,
node_color=node_color, node_size=node_size,
Expand All @@ -1187,7 +1206,7 @@ def plot_markers(node_values, node_coords, node_size='auto',
node_threshold=None, alpha=0.7, output_file=None,
display_mode="ortho", figure=None, axes=None, title=None,
annotate=True, black_bg=False, node_kwargs=None,
colorbar=True):
colorbar=True, radiological=False):
"""Plot network nodes (markers) on top of the brain glass schematics.
Nodes are color coded according to provided nodal measure. Nodal measure
Expand Down Expand Up @@ -1243,6 +1262,7 @@ def plot_markers(node_values, node_coords, node_size='auto',
the nodes in one go
%(colorbar)s
Default=True.
%(radiological)s
"""
node_values = np.array(node_values).flatten()
Expand All @@ -1258,7 +1278,8 @@ def plot_markers(node_values, node_coords, node_size='auto',

display = plot_glass_brain(None, display_mode=display_mode,
figure=figure, axes=axes, title=title,
annotate=annotate, black_bg=black_bg)
annotate=annotate, black_bg=black_bg,
radiological=radiological)

if isinstance(node_size, str) and node_size == 'auto':
node_size = min(1e4 / len(node_coords), 100)
Expand Down
Loading

0 comments on commit 99e8530

Please sign in to comment.