Alpha Threshold¶

NiiVue supports asymmetric positive and negative statistical thresholds and can render sub-threshold voxels as translucent. This feature is useful because brain activity maps are often thresholded to correct for multiple comparisons, which can reduce statistical power. Visualizing sub-threshold values helps reveal underlying trends that might otherwise be hidden.

Note that smoothing an image will erode peaks, generally reducing the number of voxels that exceed a specified threshold.

This Python notebook mirrors a JavaScript web demo.

In [1]:
import ipywidgets as widgets
from IPython.display import display
from ipyniivue import NiiVue, ShowRender, SliceType

# Create NiiVue instance with specific settings
nv = NiiVue(
    loading_text="waiting",
    back_color=(1, 1, 1, 1),
    show_3d_crosshair=True,
    is_colorbar=True,
    multiplanar_show_render=ShowRender.ALWAYS,
)

# Set initial configuration
nv.set_radiological_convention(False)
nv.set_slice_type(SliceType.MULTIPLANAR)
nv.set_slice_mm(False)
nv.set_interpolation(True)

# Load 4D volume with paired HEAD and BRIK files
nv.load_volumes(
    [
        {
            "path": "../images/fslmean.nii.gz",
        },
        {
            "path": "../images/fslt.nii.gz",
            "colormap": "warm",
            "colormap_negative": "winter",
            "cal_min": 3,
            "cal_max": 6,
            "cal_min_neg": -6,
            "cal_max_neg": -3,
        },
    ]
)


# Hide colorbar for anatomical scan
nv.volumes[0].colorbar_visible = False

# Set initial overlay outline
nv.overlay_outline_width = 0.25

# High DPI checkbox
dpi_checkbox = widgets.Checkbox(
    value=True,
    description="High DPI",
    tooltip="Higher resolution for 'retina' displays",
)

# Negative colors checkbox
negative_checkbox = widgets.Checkbox(value=True, description="Negative Colors")

# Smooth checkbox
smooth_checkbox = widgets.Checkbox(
    value=False,
    description="Smooth",
    tooltip=(
        "Trilinear interpolation blurs data, "
        "but can change which voxels survive a threshold"
    ),
)

# World space checkbox
world_checkbox = widgets.Checkbox(value=False, description="World Space")


# Outline width slider
outline_slider = widgets.IntSlider(
    min=0, max=4, value=1, description="Outline", continuous_update=True, readout=False
)

# 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:",
)

# threshold sliders

pos_stat_range = widgets.IntRangeSlider(
    value=[5, 7],
    min=1,
    max=10,
    step=1,
    description='+Threshold:',
    continuous_update=True,
    readout=False,
)

neg_stat_range = widgets.IntRangeSlider(
    value=[5, 7],
    min=1,
    max=10,
    step=1,
    description='-Threshold:',
    continuous_update=True,
    readout=False,
)
# Location display
location_output = widgets.HTML(value=" ")

## Setup Event Handlers

def on_dpi_change(change):
    """Handle DPI checkbox changes."""
    nv.set_high_resolution_capable(change["new"])

def on_negative_change(change):
    """Handle negative colormap checkbox changes."""
    neg_stat_range.disabled = not change["new"]
    if change["new"]:
        nv.set_colormap_negative(nv.volumes[1].id, "winter")
    else:
        nv.set_colormap_negative(nv.volumes[1].id, "")

def on_smooth_change(change):
    """Handle smooth interpolation checkbox changes."""
    nv.set_interpolation(not change["new"])


def on_world_change(change):
    """Handle world space checkbox changes."""
    nv.set_slice_mm(change["new"])


def on_outline_change(change):
    """Handle outline width slider changes."""
    nv.overlay_outline_width = 0.25 * change["new"]


def on_alpha_mode_change(change):
    """Handle alpha mode dropdown changes."""
    nv.volumes[1].colormap_type = change["new"]

def on_pos_stat_change(change):
    """Set threshold for statistical overlay."""
    low, high = change["new"]
    nv.volumes[1].cal_min = low
    nv.volumes[1].cal_max = high 

def on_neg_stat_change(change):
    """Set threshold for statistical overlay."""
    low, high = change["new"]
    nv.volumes[1].cal_min_neg = -low
    nv.volumes[1].cal_max_neg = -high 

@nv.on_location_change
def handle_location_change(data):
    """Update location display when crosshair moves."""
    location_output.value = f"  {data['string']}"

# Attach event handlers
dpi_checkbox.observe(on_dpi_change, names="value")
negative_checkbox.observe(on_negative_change, names="value")
smooth_checkbox.observe(on_smooth_change, names="value")
world_checkbox.observe(on_world_change, names="value")
outline_slider.observe(on_outline_change, names="value")
alpha_dropdown.observe(on_alpha_mode_change, names="value")

# Initialize values
on_outline_change({"new": outline_slider.value})
on_alpha_mode_change({"new": alpha_dropdown.value})

pos_stat_range.observe(on_pos_stat_change, names="value")
neg_stat_range.observe(on_neg_stat_change, names="value")
## Display All

# Organize controls
controls_row1 = widgets.HBox(
    [dpi_checkbox, negative_checkbox, smooth_checkbox, world_checkbox]
)
controls_row2 = widgets.HBox([neg_stat_range, pos_stat_range])
controls_row3 = widgets.HBox([outline_slider, alpha_dropdown])

# Create main layout
controls = widgets.VBox(
    [controls_row1, controls_row2, controls_row3, location_output]
)

# Display everything
display(widgets.VBox([controls, nv]))
In [ ]: