Voxel-based Statistical Mosaics¶
Brain activity statistical maps are often shown as overlays on anatomical scans to aid anatomical interpretation. Because slice views and volume renderings trade precision for interpretability, displaying both simultaneously using a mosaic provides a clearer and more informative visualization.
Note you can interactively modify the mosaic string to create your own custom images. In particular, providing lines in the renderings that correspond to slice locations can allow viewers to appreciate the context for the images. Choose axial (A), coronal (C) or sagittal (S) slices. Modify with cross slices (X), renderings (R), and horizontal overlap (H).
This notebook mirrors the NiiVue web page.
In [1]:
import ipywidgets as widgets
from IPython.display import display
from ipyniivue import NiiVue
nv = NiiVue(
is_colorbar=True,
is_alpha_clip_dark=True,
center_mosaic=True,
back_color=(1, 1, 1, 1),
)
nv.load_volumes(
[
{
"path": "../images/mni152.nii.gz",
"colorbar_visible": False,
},
{
"path": "../images/spmMotor.nii.gz",
"cal_min": 4.2,
"cal_max": 8,
"colormap": "warm",
"colormap_negative": "winter",
},
]
)
initial_mosaic = "A -20 50 60 70 C -10 -20 -50 S R X 0 R X -0"
nv.set_slice_mosaic_string(initial_mosaic)
@nv.on_canvas_attached
def setup_illumination():
"""Set volume render illumination after canvas attached."""
nv.set_volume_render_illumination(0.3)
## Create Interactive Controls
# mosaic string input
mosaic_text = widgets.Text(
value=initial_mosaic,
placeholder="Enter mosaic string",
description="Mosaic String:",
style={"description_width": "initial"},
layout=widgets.Layout(width="600px"),
)
# help button
help_button = widgets.Button(
description="Help",
button_style="info",
tooltip="Click for information about mosaic strings",
layout=widgets.Layout(width="80px"),
)
# outline slider
outline_slider = widgets.IntSlider(
min=0,
max=8,
value=0,
description="Outline:",
continuous_update=True,
)
# alpha mode dropdown
alpha_dropdown = widgets.Dropdown(
options=[
("Restrict colorbar to range", 0),
("Colorbar from 0, transparent subthreshold", 1),
("Colorbar from 0, translucent subthreshold", 2),
],
value=0,
description="Alpha Mode:",
style={"description_width": "initial"},
layout=widgets.Layout(width="400px"),
)
# glossy rendering dropdown
glossy_dropdown = widgets.Dropdown(
options=[
("Slices", -1),
("Matte", 0),
("Low", 0.3),
("Medium", 0.6),
("High", 1.0),
],
value=0.3,
description="Glossy:",
style={"description_width": "initial"},
)
# dark mode checkbox
dark_checkbox = widgets.Checkbox(
value=False,
description="Dark Mode",
)
# save button
save_button = widgets.Button(
description="Save Bitmap",
button_style="success",
tooltip="Save current view as PNG",
)
# output for messages
output = widgets.Output()
## Setup event handlers
def on_mosaic_change(change):
"""Handle mosaic string changes."""
nv.set_slice_mosaic_string(change["new"])
def on_help_click(b):
"""Display help information."""
with output:
output.clear_output()
print("Mosaic String Help:")
print("===================")
print("Choose axial (A), coronal (C) or sagittal (S) slices.")
print("Modify with:")
print(" - X: cross slices")
print(" - R: renderings")
print(" - H: horizontal overlap (e.g., H 0.3)")
print("")
print("Example: 'A -20 50 60 70 C -10 -20 -50 S R X 0 R X -0'")
print("This creates axial slices at positions -20, 50, 60, 70,")
print("coronal slices at -10, -20, -50, a sagittal slice,")
print("and renderings with cross sections.")
def on_outline_change(change):
"""Handle outline width changes."""
nv.overlay_outline_width = 0.25 * change["new"]
def on_alpha_change(change):
"""Handle alpha mode changes."""
nv.volumes[1].colormap_type = change["new"]
def on_glossy_change(change):
"""Handle glossy rendering changes."""
value = float(change["new"])
nv.set_volume_render_illumination(value)
def on_dark_change(change):
"""Handle dark mode toggle."""
if change["new"]:
nv.opts.back_color = (0, 0, 0, 1)
else:
nv.opts.back_color = (1, 1, 1, 1)
def on_save_click(b):
"""Save the current scene."""
with output:
output.clear_output()
nv.save_scene("mosaic_screenshot.png")
print("Scene saved as 'mosaic_screenshot.png'")
# attach event handlers
mosaic_text.observe(on_mosaic_change, names="value")
help_button.on_click(on_help_click)
outline_slider.observe(on_outline_change, names="value")
alpha_dropdown.observe(on_alpha_change, names="value")
glossy_dropdown.observe(on_glossy_change, names="value")
dark_checkbox.observe(on_dark_change, names="value")
save_button.on_click(on_save_click)
# initialize values
on_outline_change({"new": outline_slider.value})
on_alpha_change({"new": alpha_dropdown.value})
## Display
# organize controls
mosaic_row = widgets.HBox([mosaic_text, help_button])
controls_row1 = widgets.HBox([outline_slider, alpha_dropdown])
controls_row2 = widgets.HBox([glossy_dropdown, dark_checkbox, save_button])
# create main layout
controls = widgets.VBox([mosaic_row, controls_row1, controls_row2, output])
# display everything
display(widgets.VBox([controls, nv]))
In [ ]: