Bidirectional Sync¶

Multiple NiiVue instances can communicate with each other, enabling synchronized updates such as shared crosshair positions. This bidirectional link helps verify image alignment and ensures consistent navigation across views. This demo mirrors https://niivue.com/demos/features/sync.bidirectional.html.

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

## Create three instances

nv1 = NiiVue(
    height=400,
    multiplanar_force_render=True,
    back_color=(0.0, 0.0, 0.0, 1.0),
)

nv2 = NiiVue(
    height=400,
    multiplanar_force_render=True,
    back_color=(0.0, 0.0, 0.0, 1.0),
)

nv3 = NiiVue(
    height=400,
    multiplanar_force_render=True,
    back_color=(0.0, 0.0, 0.0, 1.0),
)


nv1.load_volumes([{"path": "../images/pcasl.nii.gz"}])
nv2.load_volumes([{"path": "../images/aal.nii.gz"}]),
nv3.load_volumes([{"path": "../images/mni152.nii.gz"}])
## Create interactive controls

layout_dropdown = widgets.Dropdown(
    options=[
        ("Auto", MultiplanarType.AUTO),
        ("Column", MultiplanarType.COLUMN),
        ("Grid", MultiplanarType.GRID),
        ("Row", MultiplanarType.ROW),
    ],
    value=MultiplanarType.AUTO,
    description="Layout:",
)

sync_dropdown = widgets.Dropdown(
    options=[
        ("Sync Disabled", 0),
        ("Sync 2D", 1),
        ("Sync 3D", 2),
        ("Sync 2D and 3D", 3),
    ],
    value=3,
    description="Broadcast:",
)

status1 = widgets.HTML(value=" ")
status2 = widgets.HTML(value=" ")
status3 = widgets.HTML(value=" ")

## Setup Event Handlers

def on_layout_change(change):
    """Handle layout dropdown changes."""
    new_layout = change["new"]
    nv1.opts.multiplanar_layout = new_layout
    nv2.opts.multiplanar_layout = new_layout
    nv3.opts.multiplanar_layout = new_layout


def on_sync_change(change):
    """Handle sync mode dropdown changes."""
    v = change["new"]
    is_2d = False
    is_3d = False

    if v % 2:  # If odd (1 or 3)
        is_2d = True
    if v > 1:  # If 2 or 3
        is_3d = True

    nv1.broadcast_to([nv2, nv3], {"2d": is_2d, "3d": is_3d})
    nv2.broadcast_to([nv1, nv3], {"2d": is_2d, "3d": is_3d})
    nv3.broadcast_to([nv1, nv2], {"2d": is_2d, "3d": is_3d})


@nv1.on_location_change
def handle_location1(data):
    """Handle location string."""
    status1.value = f"  {data['string']}"


@nv2.on_location_change
def handle_location2(data):
    """Handle location string."""
    status2.value = f"  {data['string']}"


@nv3.on_location_change
def handle_location3(data):
    """Handle location string."""
    status3.value = f"  {data['string']}"


layout_dropdown.observe(on_layout_change, names="value")
sync_dropdown.observe(on_sync_change, names="value")

on_sync_change({"new": sync_dropdown.value})

## Display all

controls = widgets.HBox([layout_dropdown, sync_dropdown])

viewers = widgets.GridspecLayout(1, 3, height="400px")
viewers[0, 0] = nv1
viewers[0, 1] = nv2
viewers[0, 2] = nv3

status_row = widgets.HBox([status1, status2, status3])

display(widgets.VBox([controls, viewers, status_row]))
In [ ]:
 
In [ ]: