Skip to content

Display volumes

The qim3d library aims to provide easy ways to explore and get insights from volumetric data.

Visualization of volumetric data.

qim3d.viz.slicer

slicer(
    volume,
    slice_axis=0,
    colormap='magma',
    min_value=None,
    max_value=None,
    image_height=3,
    image_width=3,
    display_positions=False,
    interpolation=None,
    image_size=None,
    colorbar=None,
    mask=None,
    mask_alpha=0.4,
    mask_colormap='gray',
    default_position=0.5,
    **matplotlib_imshow_kwargs,
)

Interactive tool to visualize, inspect, and scroll through 2D slices of a 3D volume.

Generates a GUI with a slider to navigate through the dataset along a specified axis. This function is essential for quality control, verifying segmentation masks, or exploring orthogonal views (axial, coronal, sagittal) of a stack.

Key Features:

  • Scrollable Interface: Automatically generates a slider for the chosen axis.
  • Overlay Support: Visualize segmentation results on top of raw data using the mask parameter.
  • Dynamic Contrast: Use colorbar='slices' to adapt intensity ranges per slice, or 'volume' for a global fixed range.

Parameters:

Name Type Description Default
volume ndarray

The 3D input data to be sliced.

required
slice_axis int

The axis to slice along (e.g., 0 for Z, 1 for Y, 2 for X).

0
colormap str or LinearSegmentedColormap

Matplotlib colormap name for the volume.

'magma'
min_value float

Minimum value for color scaling. If None, inferred from data.

None
max_value float

Maximum value for color scaling. If None, inferred from data.

None
image_height int

Height of the displayed figure.

3
image_width int

Width of the displayed figure.

3
display_positions bool

If True, displays the slice index/position on the image.

False
interpolation str

Matplotlib interpolation method (e.g., 'nearest', 'bilinear').

None
image_size int

Overrides both image_height and image_width to create a square figure.

None
colorbar str

Strategy for the color bar range.

  • 'volume': Constant range based on the global min/max of the volume.
  • 'slices': Dynamic range calculated per individual slice.
  • None: No color bar is displayed.
None
mask ndarray

A 3D segmentation mask to overlay on the volume.

None
mask_alpha float

Opacity of the mask overlay (0.0 to 1.0).

0.4
mask_colormap str

Matplotlib colormap name for the mask.

'gray'
default_position float or int

Initial slice position of the slider.

  • float: Relative position between 0.0 and 1.0 (e.g., 0.5 starts at the center).
  • int: Exact slice index.
0.5
**matplotlib_imshow_kwargs

Additional keyword arguments passed to matplotlib.pyplot.imshow.

{}

Returns:

Name Type Description
slicer_obj interactive

The interactive widget object containing the figure and slider.

Example

import qim3d

# Load sample data
vol = qim3d.examples.bone_128x128x128

# Visualize with a slider
qim3d.viz.slicer(vol, colormap='bone')
viz slicer

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def slicer(
    volume: np.ndarray,
    slice_axis: int = 0,
    colormap: str = 'magma',
    min_value: float = None,
    max_value: float = None,
    image_height: int = 3,
    image_width: int = 3,
    display_positions: bool = False,
    interpolation: str | None = None,
    image_size: int = None,
    colorbar: str = None,
    mask:np.ndarray = None,
    mask_alpha:float = 0.4,
    mask_colormap = 'gray',
    default_position:float|int = 0.5,
    **matplotlib_imshow_kwargs,
) -> widgets.interactive:
    """
    Interactive tool to visualize, inspect, and scroll through 2D slices of a 3D volume.

    Generates a GUI with a slider to navigate through the dataset along a specified axis. 
    This function is essential for quality control, verifying segmentation masks, 
    or exploring orthogonal views (axial, coronal, sagittal) of a stack.

    **Key Features:**

    * **Scrollable Interface:** Automatically generates a slider for the chosen axis.
    * **Overlay Support:** Visualize segmentation results on top of raw data using the `mask` parameter.
    * **Dynamic Contrast:** Use `colorbar='slices'` to adapt intensity ranges per slice, or `'volume'` for a global fixed range.

    Args:
        volume (numpy.ndarray): The 3D input data to be sliced.
        slice_axis (int, optional): The axis to slice along (e.g., 0 for Z, 1 for Y, 2 for X).
        colormap (str or matplotlib.colors.LinearSegmentedColormap, optional): Matplotlib colormap name for the volume.
        min_value (float, optional): Minimum value for color scaling. If `None`, inferred from data.
        max_value (float, optional): Maximum value for color scaling. If `None`, inferred from data.
        image_height (int, optional): Height of the displayed figure.
        image_width (int, optional): Width of the displayed figure.
        display_positions (bool, optional): If `True`, displays the slice index/position on the image.
        interpolation (str, optional): Matplotlib interpolation method (e.g., 'nearest', 'bilinear').
        image_size (int, optional): Overrides both `image_height` and `image_width` to create a square figure.
        colorbar (str, optional): Strategy for the color bar range.

            * `'volume'`: Constant range based on the global min/max of the volume.
            * `'slices'`: Dynamic range calculated per individual slice.
            * `None`: No color bar is displayed.

        mask (numpy.ndarray, optional): A 3D segmentation mask to overlay on the volume.
        mask_alpha (float, optional): Opacity of the mask overlay (0.0 to 1.0).
        mask_colormap (str, optional): Matplotlib colormap name for the mask.
        default_position (float or int, optional): Initial slice position of the slider.

            * **float**: Relative position between 0.0 and 1.0 (e.g., `0.5` starts at the center).
            * **int**: Exact slice index.

        **matplotlib_imshow_kwargs: Additional keyword arguments passed to `matplotlib.pyplot.imshow`.

    Returns:
        slicer_obj (ipywidgets.interactive):
            The interactive widget object containing the figure and slider.

    Example:
        ```python
        import qim3d

        # Load sample data
        vol = qim3d.examples.bone_128x128x128

        # Visualize with a slider
        qim3d.viz.slicer(vol, colormap='bone')
        ```
        ![viz slicer](../../assets/screenshots/viz-slicer.gif)
    """

    if image_size:
        image_height = image_size
        image_width = image_size

    colorbar_options = [None, 'slices', 'volume']
    if colorbar not in colorbar_options:
        msg = (
            f"Unrecognized value '{colorbar}' for parameter colorbar. "
            f'Expected one of {colorbar_options}.'
        )
        raise ValueError(msg)
    show_colorbar = colorbar is not None
    if colorbar == 'slices':
        # Precompute the minimum and maximum along each slice for faster widget sliding.
        non_slice_axes = tuple(i for i in range(volume.ndim) if i != slice_axis)
        slice_mins = np.min(volume, axis=non_slice_axes)
        slice_maxs = np.max(volume, axis=non_slice_axes)

    # Create the interactive widget
    def _slicer(slice_positions: int) -> Figure:
        if colorbar == 'slices':
            dynamic_min = slice_mins[slice_positions]
            dynamic_max = slice_maxs[slice_positions]
        else:
            dynamic_min = min_value
            dynamic_max = max_value

        fig = slices_grid(
            volume,
            slice_axis=slice_axis,
            colormap=colormap,
            min_value=dynamic_min,
            max_value=dynamic_max,
            image_height=image_height,
            image_width=image_width,
            display_positions=display_positions,
            interpolation=interpolation,
            slice_positions=slice_positions,
            n_slices=1,
            display_figure=True,
            colorbar=show_colorbar,
            mask = mask,
            mask_alpha = mask_alpha,
            mask_colormap = mask_colormap,
            **matplotlib_imshow_kwargs,
        )
        return fig

    if isinstance(default_position, float):
        default_position = int(default_position * (volume.shape[slice_axis] - 1))
    if isinstance(default_position, int):
        if default_position < 0:
            default_position = volume.shape[slice_axis] - default_position
        default_position = np.clip(
            default_position, a_min=0, a_max=volume.shape[slice_axis] - 1
        )
    else:
        default_position = volume.shape[slice_axis] // 2

    position_slider = widgets.IntSlider(
        value=default_position,
        min=0,
        max=volume.shape[slice_axis] - 1,
        description='Slice',
        continuous_update=True,
    )
    slicer_obj = widgets.interactive(_slicer, slice_positions=position_slider)
    slicer_obj.layout = widgets.Layout(align_items='flex-start')

    return slicer_obj

qim3d.viz.slices_grid

slices_grid(
    volume,
    slice_axis=0,
    slice_positions=None,
    n_slices=15,
    max_columns=5,
    colormap='magma',
    min_value=None,
    max_value=None,
    image_size=None,
    image_height=2,
    image_width=2,
    display_figure=False,
    display_positions=True,
    interpolation=None,
    colorbar=False,
    colorbar_style='small',
    mask=None,
    mask_alpha=0.4,
    mask_colormap='gray',
    **matplotlib_imshow_kwargs,
)

Creates a static grid visualization (montage) of multiple 2D slices from a 3D volume.

Generates a mosaic or gallery view of the dataset, ideal for reports, publications, or quick overviews. Unlike interactive tools, this function produces a static matplotlib figure that can be saved easily. It supports batch visualization of specific indices, relative positions (e.g., 'mid'), or automatically spaced intervals.

Key Features:

  • Flexible Selection: Choose slices by specific index, relative strings ('start', 'mid', 'end'), or automatic linear spacing.
  • Publication Ready: Control layout (max_columns), sizing, and colorbars for export-ready figures.
  • Mask Overlays: Superimpose segmentation masks directly onto the slice grid.

Parameters:

Name Type Description Default
volume ndarray

The 3D input volume to be sliced.

required
slice_axis int

The axis to slice along (e.g., 0 for Z, 1 for Y, 2 for X).

0
slice_positions int, list[int], str, or None

Determines which slices to display.

  • None: Displays n_slices linearly spaced across the volume.
  • list[int]: Displays exactly the slices at these indices (n_slices is ignored).
  • int: Displays n_slices centered around this specific index.
  • str: Displays n_slices around a relative position ('start', 'mid', 'end').
None
n_slices int

The number of slices to display. Ignored if slice_positions is a list.

15
max_columns int

The maximum number of columns in the grid layout.

5
colormap str or LinearSegmentedColormap

Matplotlib colormap name.

'magma'
min_value float

Minimum value for color scaling. If None, inferred from data.

None
max_value float

Maximum value for color scaling. If None, inferred from data.

None
image_size int

Overrides both image_height and image_width to create square subplots.

None
image_height int

Height of each subplot in inches.

2
image_width int

Width of each subplot in inches.

2
display_figure bool

If True, calls plt.show() immediately.

False
display_positions bool

If True, adds text labels indicating slice index and axis.

True
interpolation str

Matplotlib interpolation method (e.g., 'nearest', 'bilinear').

None
colorbar bool

If True, adds a global colorbar to the figure.

False
colorbar_style str

Visual style of the colorbar.

  • 'small': Matches the height of a single image row.
  • 'large': Spans the full height of the grid.
'small'
mask ndarray

A 3D segmentation mask to overlay on the slices.

None
mask_alpha float

Opacity of the mask overlay (0.0 to 1.0).

0.4
mask_colormap str

Matplotlib colormap name for the mask.

'gray'
**matplotlib_imshow_kwargs

Additional keyword arguments passed to matplotlib.pyplot.imshow.

{}

Returns:

Name Type Description
fig Figure

The generated matplotlib figure object containing the grid of slices.

Raises:

Type Description
ValueError

If volume is not 3D or if slice_axis is invalid.

ValueError

If slice_positions is an invalid string.

ValueError

If colorbar_style is not 'small' or 'large'.

Example

import qim3d

# Load sample data
vol = qim3d.examples.shell_225x128x128

# Create a grid of 15 linearly spaced slices
qim3d.viz.slices_grid(vol, n_slices=15)
Grid of slices

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def slices_grid(
    volume: np.ndarray,
    slice_axis: int = 0,
    slice_positions: str | int | list[int] | None = None,
    n_slices: int = 15,
    max_columns: int = 5,
    colormap: str = 'magma',
    min_value: float = None,
    max_value: float = None,
    image_size: int = None,
    image_height: int = 2,
    image_width: int = 2,
    display_figure: bool = False,
    display_positions: bool = True,
    interpolation: str | None = None,
    colorbar: bool = False,
    colorbar_style: str = 'small',
    mask:np.ndarray = None,
    mask_alpha:float = 0.4,
    mask_colormap:str = 'gray',
    **matplotlib_imshow_kwargs,
) -> matplotlib.figure.Figure:
    """
    Creates a static grid visualization (montage) of multiple 2D slices from a 3D volume.

    Generates a mosaic or gallery view of the dataset, ideal for reports, publications, or quick overviews. 
    Unlike interactive tools, this function produces a static `matplotlib` figure that can be saved easily. 
    It supports batch visualization of specific indices, relative positions (e.g., 'mid'), or automatically spaced intervals.

    **Key Features:**

    * **Flexible Selection:** Choose slices by specific index, relative strings ('start', 'mid', 'end'), or automatic linear spacing.
    * **Publication Ready:** Control layout (`max_columns`), sizing, and colorbars for export-ready figures.
    * **Mask Overlays:** Superimpose segmentation masks directly onto the slice grid.

    Args:
        volume (numpy.ndarray): The 3D input volume to be sliced.
        slice_axis (int, optional): The axis to slice along (e.g., 0 for Z, 1 for Y, 2 for X).
        slice_positions (int, list[int], str, or None, optional): Determines which slices to display.

            * **None**: Displays `n_slices` linearly spaced across the volume.
            * **list[int]**: Displays exactly the slices at these indices (`n_slices` is ignored).
            * **int**: Displays `n_slices` centered around this specific index.
            * **str**: Displays `n_slices` around a relative position ('start', 'mid', 'end').

        n_slices (int, optional): The number of slices to display. Ignored if `slice_positions` is a list.
        max_columns (int, optional): The maximum number of columns in the grid layout.
        colormap (str or matplotlib.colors.LinearSegmentedColormap, optional): Matplotlib colormap name.
        min_value (float, optional): Minimum value for color scaling. If `None`, inferred from data.
        max_value (float, optional): Maximum value for color scaling. If `None`, inferred from data.
        image_size (int, optional): Overrides both `image_height` and `image_width` to create square subplots.
        image_height (int, optional): Height of each subplot in inches.
        image_width (int, optional): Width of each subplot in inches.
        display_figure (bool, optional): If `True`, calls `plt.show()` immediately.
        display_positions (bool, optional): If `True`, adds text labels indicating slice index and axis.
        interpolation (str, optional): Matplotlib interpolation method (e.g., 'nearest', 'bilinear').
        colorbar (bool, optional): If `True`, adds a global colorbar to the figure.
        colorbar_style (str, optional): Visual style of the colorbar.

            * `'small'`: Matches the height of a single image row.
            * `'large'`: Spans the full height of the grid.

        mask (numpy.ndarray, optional): A 3D segmentation mask to overlay on the slices.
        mask_alpha (float, optional): Opacity of the mask overlay (0.0 to 1.0).
        mask_colormap (str, optional): Matplotlib colormap name for the mask.
        **matplotlib_imshow_kwargs: Additional keyword arguments passed to `matplotlib.pyplot.imshow`.

    Returns:
        fig (matplotlib.figure.Figure):
            The generated matplotlib figure object containing the grid of slices.

    Raises:
        ValueError: If `volume` is not 3D or if `slice_axis` is invalid.
        ValueError: If `slice_positions` is an invalid string.
        ValueError: If `colorbar_style` is not 'small' or 'large'.

    Example:
        ```python
        import qim3d

        # Load sample data
        vol = qim3d.examples.shell_225x128x128

        # Create a grid of 15 linearly spaced slices
        qim3d.viz.slices_grid(vol, n_slices=15)
        ```
        ![Grid of slices](../../assets/screenshots/viz-slices.png)
    """
    if image_size:
        image_height = image_size
        image_width = image_size

    # If we pass python None to the imshow function, it will set to
    # default value 'antialiased'
    if interpolation is None:
        interpolation = 'none'

    # Numpy array or Torch tensor input
    if not isinstance(volume, np.ndarray | da.Array):
        msg = 'Data type not supported'
        raise ValueError(msg)

    if volume.ndim < 3:
        msg = 'The provided object is not a volume as it has less than 3 dimensions.'
        raise ValueError(msg)

    colorbar_style_options = ['small', 'large']
    if colorbar_style not in colorbar_style_options:
        msg = f"Value '{colorbar_style}' is not valid for colorbar style. Please select from {colorbar_style_options}."
        raise ValueError(msg)

    if isinstance(volume, da.Array):
        volume = volume.compute()

    # Ensure axis is a valid choice
    if not (0 <= slice_axis < volume.ndim):
        msg = f"Invalid value for 'slice_axis'. It should be an integer between 0 and {volume.ndim - 1}."
        raise ValueError(msg)

    # Here we deal with the case that the user wants to use the objects colormap directly
    if (
        type(colormap) == matplotlib.colors.LinearSegmentedColormap
        or colormap == 'segmentation'
    ):
        num_labels = volume.max()

        if colormap == 'segmentation':
            colormap = qim3d.viz.colormaps.segmentation(num_labels)
        # If min_value and max_value are not set like this, then in case the
        # number of objects changes on new slice, objects might change
        # colors. So when using a slider, the same object suddently
        # changes color (flickers), which is confusing and annoying.
        min_value = 0
        max_value = num_labels

    # Get total number of slices in the specified dimension
    n_total = volume.shape[slice_axis]

    # Position is not provided - will use linearly spaced slices
    if slice_positions is None:
        slice_idxs = np.linspace(0, n_total - 1, n_slices, dtype=int)
    # Position is a string
    elif isinstance(slice_positions, str) and slice_positions.lower() in [
        'start',
        'mid',
        'end',
    ]:
        if slice_positions.lower() == 'start':
            slice_idxs = _get_slice_range(0, n_slices, n_total)
        elif slice_positions.lower() == 'mid':
            slice_idxs = _get_slice_range(n_total // 2, n_slices, n_total)
        elif slice_positions.lower() == 'end':
            slice_idxs = _get_slice_range(n_total - 1, n_slices, n_total)
    #  Position is an integer
    elif isinstance(slice_positions, int):
        slice_idxs = _get_slice_range(slice_positions, n_slices, n_total)
    # Position is a list of integers
    elif isinstance(slice_positions, list) and all(map(lambda x:isinstance(x, int), slice_positions)):
        slice_idxs = np.array(slice_positions)
        if any(slice_idxs < 0):
            dim = volume.shape[slice_axis]
            slice_idxs[np.where(slice_idxs < 0)] += dim
        n_slices = len(slice_idxs)


    else:
        msg = 'Position not recognized. Choose an integer, list of integers or one of the following strings: "start", "mid" or "end".'
        raise ValueError(msg)

    # Make grid
    nrows = math.ceil(n_slices / max_columns)
    ncols = min(n_slices, max_columns)

    # Generate figure
    fig, axs = plt.subplots(
        nrows=nrows,
        ncols=ncols,
        figsize=(ncols * image_height, nrows * image_width),
        constrained_layout=True,
    )

    if nrows == 1:
        axs = [axs]  # Convert to a list for uniformity

    # Convert to NumPy array in order to use the numpy.take method
    if isinstance(volume, da.Array):
        volume = volume.compute()

    if colorbar:
        # In this case, we want the vrange to be constant across the
        # slices, which makes them all comparable to a single colorbar.
        new_min_value = min_value if min_value is not None else np.min(volume)
        new_max_value = max_value if max_value is not None else np.max(volume)

    # Run through each ax of the grid
    for i, ax_row in enumerate(axs):
        for j, ax in enumerate(np.atleast_1d(ax_row)):
            slice_idx = i * max_columns + j
            try:
                slice_img = volume.take(slice_idxs[slice_idx], axis=slice_axis)
                slice_mask = (
                    None
                    if mask is None
                    else mask.take(slice_idxs[slice_idx], axis=slice_axis)
                )

                if not colorbar:
                    # If min_value is higher than the highest value in the
                    # image ValueError is raised. We don't want to
                    # override the values because next slices might be okay
                    new_min_value = (
                        None
                        if (
                            isinstance(min_value, float | int)
                            and min_value > np.max(slice_img)
                        )
                        else min_value
                    )
                    new_max_value = (
                        None
                        if (
                            isinstance(max_value, float | int)
                            and max_value < np.min(slice_img)
                        )
                        else max_value
                    )

                ax.imshow(
                    slice_img,
                    cmap=colormap,
                    interpolation=interpolation,
                    vmin=new_min_value,
                    vmax=new_max_value,
                    **matplotlib_imshow_kwargs,
                )
                if slice_mask is not None:
                    ax.imshow(slice_mask, cmap = mask_colormap, alpha = mask_alpha)

                if display_positions:
                    ax.text(
                        0.0,
                        1.0,
                        f'slice {slice_idxs[slice_idx]} ',
                        transform=ax.transAxes,
                        color='white',
                        fontsize=8,
                        va='top',
                        ha='left',
                        bbox={'facecolor': '#303030', 'linewidth': 0, 'pad': 0},
                    )

                    ax.text(
                        1.0,
                        0.0,
                        f'axis {slice_axis} ',
                        transform=ax.transAxes,
                        color='white',
                        fontsize=8,
                        va='bottom',
                        ha='right',
                        bbox={'facecolor': '#303030', 'linewidth': 0, 'pad': 0},
                    )

            except IndexError:
                # Not a problem, because we simply do not have a slice to show
                pass

            # Hide the axis, so that we have a nice grid
            ax.axis('off')

    if colorbar:
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', category=UserWarning)
            fig.tight_layout()

        norm = matplotlib.colors.Normalize(
            vmin=new_min_value, vmax=new_max_value, clip=True
        )
        mappable = matplotlib.cm.ScalarMappable(norm=norm, cmap=colormap)

        if colorbar_style == 'small':
            # Figure coordinates of top-right axis
            tr_pos = np.atleast_1d(axs[0])[-1].get_position()
            # The width is divided by ncols to make it the same relative size to the images
            colorbar_ax = fig.add_axes(
                [tr_pos.x1 + 0.05 / ncols, tr_pos.y0, 0.05 / ncols, tr_pos.height]
            )
            fig.colorbar(mappable=mappable, cax=colorbar_ax, orientation='vertical')
        elif colorbar_style == 'large':
            # Figure coordinates of bottom- and top-right axis
            br_pos = np.atleast_1d(axs[-1])[-1].get_position()
            tr_pos = np.atleast_1d(axs[0])[-1].get_position()
            # The width is divided by ncols to make it the same relative size to the images
            colorbar_ax = fig.add_axes(
                [
                    br_pos.xmax + 0.05 / ncols,
                    br_pos.y0 + 0.0015,
                    0.05 / ncols,
                    (tr_pos.y1 - br_pos.y0) - 0.0015,
                ]
            )
            fig.colorbar(mappable=mappable, cax=colorbar_ax, orientation='vertical')

    if display_figure:
        plt.show()

    plt.close()

    return fig

qim3d.viz.planes

planes(
    volume, colormap='magma', min_value=None, max_value=None
)

Displays an interactive 3D scene with movable orthogonal cross-sections (X, Y, Z planes).

Creates a composite 3D viewer where three orthogonal slices intersect within the volume. Users can interactively drag sliders to explore the internal structure of the stack from different angles simultaneously. This visualization is often referred to as Multi-Planar Reconstruction (MPR) or an Orthogonal Slicer.

Key Features:

  • 3D Context: Visualizes how the three planes (Axial, Coronal, Sagittal) intersect in 3D space.
  • Interactive Controls: Includes sliders for position, opacity, and dynamic color range adjustment.
  • High Performance: Uses Plotly and ipywidgets for responsive slicing of local data.

Parameters:

Name Type Description Default
volume ndarray

The 3D input volume.

required
colormap str or Colormap

Matplotlib colormap name (e.g., 'magma', 'viridis').

'magma'
min_value float

Minimum value for color scaling (lower bound of contrast).

None
max_value float

Maximum value for color scaling (upper bound of contrast).

None
Example

import qim3d

# Load sample data
vol = qim3d.examples.shell_225x128x128

# Launch the interactive 3D plane viewer
qim3d.viz.planes(vol, colormap='plasma')
viz planes

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def planes(
    volume: np.ndarray,
    colormap: str | matplotlib.colors.Colormap = 'magma',
    min_value: float = None,
    max_value: float = None,
) -> None:
    """
    Displays an interactive 3D scene with movable orthogonal cross-sections (X, Y, Z planes).

    Creates a composite 3D viewer where three orthogonal slices intersect within the volume. 
    Users can interactively drag sliders to explore the internal structure of the stack from different angles simultaneously. 
    This visualization is often referred to as Multi-Planar Reconstruction (MPR) or an Orthogonal Slicer.

    **Key Features:**

    * **3D Context:** Visualizes how the three planes (Axial, Coronal, Sagittal) intersect in 3D space.
    * **Interactive Controls:** Includes sliders for position, opacity, and dynamic color range adjustment.
    * **High Performance:** Uses `Plotly` and `ipywidgets` for responsive slicing of local data.

    Args:
        volume (numpy.ndarray): The 3D input volume.
        colormap (str or matplotlib.colors.Colormap, optional): Matplotlib colormap name (e.g., 'magma', 'viridis').
        min_value (float, optional): Minimum value for color scaling (lower bound of contrast).
        max_value (float, optional): Maximum value for color scaling (upper bound of contrast).

    Example:
        ```python
        import qim3d

        # Load sample data
        vol = qim3d.examples.shell_225x128x128

        # Launch the interactive 3D plane viewer
        qim3d.viz.planes(vol, colormap='plasma')
        ```
        ![viz planes](../../assets/screenshots/viz-planes.gif)
    """
    VolumePlaneSlicer(
        volume=volume, colormap=colormap, color_range=[min_value, max_value]
    ).show()

qim3d.viz.slicer_orthogonal

slicer_orthogonal(
    volume,
    colormap='magma',
    min_value=None,
    max_value=None,
    image_height=3,
    image_width=3,
    display_positions=False,
    interpolation=None,
    image_size=None,
    colorbar=None,
    mask=None,
    mask_alpha=0.4,
    mask_colormap='gray',
    default_z=0.5,
    default_y=0.5,
    default_x=0.5,
)

Interactive tool to visualize three orthogonal views (Z, Y, X) side-by-side.

Creates a composite widget displaying Axial, Coronal, and Sagittal slices simultaneously. This is often called a Multi-Planar Reconstruction (MPR) view. It allows users to verify isotropy, check feature continuity across dimensions, or inspect segmentation masks in all three orientations at once.

Key Features:

  • Simultaneous Views: Generates three independent sliders for Z, Y, and X axes.
  • Linked Settings: Applies colormaps, contrast settings, and masks uniformly across all three views.
  • Holistic Inspection: Essential for understanding the 3D structure without rendering a full 3D scene.

Parameters:

Name Type Description Default
volume ndarray

The 3D input volume.

required
colormap str or LinearSegmentedColormap

Matplotlib colormap name.

'magma'
min_value float

Minimum value for color scaling. If None, inferred from data.

None
max_value float

Maximum value for color scaling. If None, inferred from data.

None
image_height int

Height of each individual figure in inches.

3
image_width int

Width of each individual figure in inches.

3
display_positions bool

If True, displays the slice index on each image.

False
interpolation str

Matplotlib interpolation method (e.g., 'nearest', 'bilinear').

None
image_size int

Overrides image_height and image_width to make figures square.

None
colorbar str

Strategy for the color bar range.

  • 'volume': Constant range based on the global min/max of the volume.
  • 'slices': Dynamic range calculated per individual slice.
  • None: No color bar is displayed.
None
mask ndarray

A 3D segmentation mask to overlay on all views.

None
mask_alpha float

Opacity of the mask overlay (0.0 to 1.0).

0.4
mask_colormap str

Matplotlib colormap name for the mask.

'gray'
default_z float or int

Initial position for the Z-axis slider (0.0-1.0 relative or exact index).

0.5
default_y float or int

Initial position for the Y-axis slider (0.0-1.0 relative or exact index).

0.5
default_x float or int

Initial position for the X-axis slider (0.0-1.0 relative or exact index).

0.5

Returns:

Name Type Description
slicer_orthogonal_obj HBox

A container widget holding the three interactive slicers arranged horizontally.

Example

import qim3d

# Load sample data
vol = qim3d.examples.fly_150x256x256

# View all three axes side-by-side
qim3d.viz.slicer_orthogonal(vol, colormap="magma")
viz slicer_orthogonal

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def slicer_orthogonal(
    volume: np.ndarray,
    colormap: str = 'magma',
    min_value: float = None,
    max_value: float = None,
    image_height: int = 3,
    image_width: int = 3,
    display_positions: bool = False,
    interpolation: str | None = None,
    image_size: int = None,
    colorbar:str = None,
    mask:np.ndarray = None,
    mask_alpha:float = 0.4,
    mask_colormap:str = 'gray',
    default_z:float|int = 0.5,
    default_y:float|int = 0.5,
    default_x:float|int = 0.5,
) -> widgets.interactive:
    """
    Interactive tool to visualize three orthogonal views (Z, Y, X) side-by-side.

    Creates a composite widget displaying Axial, Coronal, and Sagittal slices simultaneously. 
    This is often called a Multi-Planar Reconstruction (MPR) view. It allows users to verify isotropy, 
    check feature continuity across dimensions, or inspect segmentation masks in all three orientations at once.

    **Key Features:**

    * **Simultaneous Views:** Generates three independent sliders for Z, Y, and X axes.
    * **Linked Settings:** Applies colormaps, contrast settings, and masks uniformly across all three views.
    * **Holistic Inspection:** Essential for understanding the 3D structure without rendering a full 3D scene.

    Args:
        volume (numpy.ndarray): The 3D input volume.
        colormap (str or matplotlib.colors.LinearSegmentedColormap, optional): Matplotlib colormap name.
        min_value (float, optional): Minimum value for color scaling. If `None`, inferred from data.
        max_value (float, optional): Maximum value for color scaling. If `None`, inferred from data.
        image_height (int, optional): Height of each individual figure in inches.
        image_width (int, optional): Width of each individual figure in inches.
        display_positions (bool, optional): If `True`, displays the slice index on each image.
        interpolation (str, optional): Matplotlib interpolation method (e.g., 'nearest', 'bilinear').
        image_size (int, optional): Overrides `image_height` and `image_width` to make figures square.
        colorbar (str, optional): Strategy for the color bar range.

            * `'volume'`: Constant range based on the global min/max of the volume.
            * `'slices'`: Dynamic range calculated per individual slice.
            * `None`: No color bar is displayed.

        mask (numpy.ndarray, optional): A 3D segmentation mask to overlay on all views.
        mask_alpha (float, optional): Opacity of the mask overlay (0.0 to 1.0).
        mask_colormap (str, optional): Matplotlib colormap name for the mask.
        default_z (float or int, optional): Initial position for the Z-axis slider (0.0-1.0 relative or exact index).
        default_y (float or int, optional): Initial position for the Y-axis slider (0.0-1.0 relative or exact index).
        default_x (float or int, optional): Initial position for the X-axis slider (0.0-1.0 relative or exact index).

    Returns:
        slicer_orthogonal_obj (ipywidgets.HBox):
            A container widget holding the three interactive slicers arranged horizontally.

    Example:
        ```python
        import qim3d

        # Load sample data
        vol = qim3d.examples.fly_150x256x256

        # View all three axes side-by-side
        qim3d.viz.slicer_orthogonal(vol, colormap="magma")
        ```
        ![viz slicer_orthogonal](../../assets/screenshots/viz-orthogonal.gif)
    """

    if image_size:
        image_height = image_size
        image_width = image_size

    get_slicer_for_axis = lambda slice_axis, default_position: slicer(
        volume,
        slice_axis=slice_axis,
        colormap=colormap,
        min_value=min_value,
        max_value=max_value,
        image_height=image_height,
        image_width=image_width,
        display_positions=display_positions,
        interpolation=interpolation,
        colorbar=colorbar,
        mask = mask,
        mask_alpha = mask_alpha,
        mask_colormap = mask_colormap,
        default_position=default_position
    )

    z_slicer = get_slicer_for_axis(slice_axis=0, default_position=default_z)
    y_slicer = get_slicer_for_axis(slice_axis=1, default_position=default_y)
    x_slicer = get_slicer_for_axis(slice_axis=2, default_position=default_x)

    z_slicer.children[0].description = 'Z'
    y_slicer.children[0].description = 'Y'
    x_slicer.children[0].description = 'X'

    return widgets.HBox([z_slicer, y_slicer, x_slicer])

qim3d.viz.volumetric

volumetric(
    volume,
    aspectmode='data',
    show=True,
    save=False,
    grid_visible=False,
    colormap='magma',
    constant_opacity=False,
    opacity_function=None,
    min_value=None,
    max_value=None,
    samples='auto',
    max_voxels=256**3,
    data_type='scaled_float16',
    camera_mode='orbit',
    **kwargs,
)

Renders a 3D volume using high-performance hardware-accelerated ray-casting.

Creates an interactive 3D visualization in the browser using K3D. This function is ideal for inspecting complex voxel data, understanding 3D spatial relationships, or creating exportable HTML representations of a stack. It handles large datasets by automatically downsampling if the size exceeds a set threshold.

Key Features:

  • Browser-Based: Renders directly in Jupyter notebooks or exports to standalone HTML.
  • Performance: Automatically manages sampling rates and data types (float16) for smooth interaction.
  • Customization: Supports custom colormaps, opacity transfer functions, and camera modes.

Parameters:

Name Type Description Default
volume ndarray

The 3D input data to be rendered.

required
aspectmode str

Controls the proportions of the scene axes.

  • 'data': Axes are drawn in proportion to the physical data range.
  • 'cube': Axes are constrained to a cube regardless of data range.
'data'
show bool

If True, displays the plot immediately.

True
save bool or str

Controls saving the output.

  • str: Saves the visualization to the specified HTML file path.
  • True: Saves as 'snapshot.html' (default behavior of save).
  • False: Does not save the file.
False
grid_visible bool

If True, displays a grid around the volume.

False
colormap str, matplotlib.colors.Colormap, or list

Colormap for the rendering. Can be a Matplotlib name (e.g., 'magma') or object.

'magma'
constant_opacity bool

Deprecated. Use opacity_function='constant' instead.

False
opacity_function str or list

Defines the transparency transfer function.

  • 'constant': Sets a uniform opacity for segmentation masks.
  • list: A specific list defining the opacity curve.
None
min_value float

Minimum value for color scaling. If None, inferred from data.

None
max_value float

Maximum value for color scaling. If None, inferred from data.

None
samples int or str

Number of ray-marching samples.

  • 'auto': Calculates optimal samples based on volume size.
  • int: Specific number of samples (lower is faster, higher is better quality).
'auto'
max_voxels int

Maximum number of voxels allowed before downsampling occurs (defaults to approx. 16 million).

256 ** 3
data_type str

Internal data type for rendering. 'scaled_float16' reduces memory usage.

'scaled_float16'
camera_mode str

Interaction mode for the camera ('orbit', 'trackball', or 'fly').

'orbit'
**kwargs

Additional keyword arguments passed to k3d.plot.

{}

Returns:

Name Type Description
plot Plot

The K3D plot object. Returned if show=False.

Raises:

Type Description
ValueError

If aspectmode is not 'data' or 'cube'.

ValueError

If camera_mode is not 'orbit', 'trackball', or 'fly'.

Tip

The function can be used for object label visualization using a colormap created with qim3d.viz.colormaps.objects along with setting objects=True. The latter ensures appropriate rendering.

Example

Display a volume inline:

import qim3d

vol = qim3d.examples.bone_128x128x128
qim3d.viz.volumetric(vol)

Save the rendering to an HTML file without displaying it:

plot = qim3d.viz.volumetric(vol, show=False, save="my_render.html")

Source code in qim3d/viz/_k3d.py
@coarseness('volume')
def volumetric(
    volume: np.ndarray,
    aspectmode: str = 'data',
    show: bool = True,
    save: bool = False,
    grid_visible: bool = False,
    colormap: str = 'magma',
    constant_opacity: bool = False,
    opacity_function: str | list = None,
    min_value: float | None = None,
    max_value: float | None = None,
    samples: int | str = 'auto',
    max_voxels: int = 256**3,
    data_type: str = 'scaled_float16',
    camera_mode: str = 'orbit',
    **kwargs,
) -> k3d.Plot | None:
    """
    Renders a 3D volume using high-performance hardware-accelerated ray-casting.

    Creates an interactive 3D visualization in the browser using K3D. This function is ideal for inspecting complex voxel data, understanding 3D spatial relationships, or creating exportable HTML representations of a stack. It handles large datasets by automatically downsampling if the size exceeds a set threshold.

    **Key Features:**

    * **Browser-Based:** Renders directly in Jupyter notebooks or exports to standalone HTML.
    * **Performance:** Automatically manages sampling rates and data types (`float16`) for smooth interaction.
    * **Customization:** Supports custom colormaps, opacity transfer functions, and camera modes.

    Args:
        volume (numpy.ndarray): The 3D input data to be rendered.
        aspectmode (str, optional): Controls the proportions of the scene axes.

            * `'data'`: Axes are drawn in proportion to the physical data range.
            * `'cube'`: Axes are constrained to a cube regardless of data range.

        show (bool, optional): If `True`, displays the plot immediately.
        save (bool or str, optional): Controls saving the output.

            * **str**: Saves the visualization to the specified HTML file path.
            * **True**: Saves as 'snapshot.html' (default behavior of save).
            * **False**: Does not save the file.

        grid_visible (bool, optional): If `True`, displays a grid around the volume.
        colormap (str, matplotlib.colors.Colormap, or list, optional): Colormap for the rendering. Can be a Matplotlib name (e.g., 'magma') or object.
        constant_opacity (bool, optional): **Deprecated**. Use `opacity_function='constant'` instead.
        opacity_function (str or list, optional): Defines the transparency transfer function.

            * `'constant'`: Sets a uniform opacity for segmentation masks.
            * **list**: A specific list defining the opacity curve.

        min_value (float, optional): Minimum value for color scaling. If `None`, inferred from data.
        max_value (float, optional): Maximum value for color scaling. If `None`, inferred from data.
        samples (int or str, optional): Number of ray-marching samples.

            * `'auto'`: Calculates optimal samples based on volume size.
            * **int**: Specific number of samples (lower is faster, higher is better quality).

        max_voxels (int, optional): Maximum number of voxels allowed before downsampling occurs (defaults to approx. 16 million).
        data_type (str, optional): Internal data type for rendering. `'scaled_float16'` reduces memory usage.
        camera_mode (str, optional): Interaction mode for the camera (`'orbit'`, `'trackball'`, or `'fly'`).
        **kwargs: Additional keyword arguments passed to `k3d.plot`.

    Returns:
        plot (k3d.Plot):
            The K3D plot object. Returned if `show=False`.

    Raises:
        ValueError: If `aspectmode` is not 'data' or 'cube'.
        ValueError: If `camera_mode` is not 'orbit', 'trackball', or 'fly'.

    Tip:
        The function can be used for object label visualization using a `colormap` created with `qim3d.viz.colormaps.objects` along with setting `objects=True`. The latter ensures appropriate rendering.

    Example:
        Display a volume inline:
        ```python
        import qim3d

        vol = qim3d.examples.bone_128x128x128
        qim3d.viz.volumetric(vol)
        ```
        <iframe src="https://platform.qim.dk/k3d/fima-bone_128x128x128-20240221113459.html" width="100%" height="500" frameborder="0"></iframe>

        Save the rendering to an HTML file without displaying it:
        ```python
        plot = qim3d.viz.volumetric(vol, show=False, save="my_render.html")
        ```
    """

    pixel_count = volume.shape[0] * volume.shape[1] * volume.shape[2]
    # target is 60fps on m1 macbook pro, using test volume: https://data.qim.dk/pages/foam.html
    if samples == 'auto':
        y1, x1 = 256, 16777216  # 256 samples at res 256*256*256=16.777.216
        y2, x2 = 32, 134217728  # 32 samples at res 512*512*512=134.217.728

        # we fit linear function to the two points
        a = (y1 - y2) / (x1 - x2)
        b = y1 - a * x1

        samples = int(min(max(a * pixel_count + b, 64), 512))
    else:
        samples = int(samples)  # make sure it's an integer

    if aspectmode.lower() not in ['data', 'cube']:
        msg = "aspectmode should be either 'data' or 'cube'"
        raise ValueError(msg)

    if camera_mode not in ['orbit', 'trackball', 'fly']:
        msg = "camera_mode should be either 'orbit', 'trackbal' or 'fly'"
        raise ValueError(msg)

    # check if image should be downsampled for visualization
    original_shape = volume.shape
    volume = downscale_img(volume, max_voxels=max_voxels)

    new_shape = volume.shape

    if original_shape != new_shape:
        log.warning(
            f'Downsampled image for visualization, from {original_shape} to {new_shape}'
        )

    # Scale the image to float16 if needed
    if save:
        # When saving, we need float64
        volume = volume.astype(np.float64)
    else:
        if data_type == 'scaled_float16':
            volume = scale_to_float16(volume)
        else:
            volume = volume.astype(data_type)

    # Set color ranges
    color_range = [np.min(volume), np.max(volume)]
    if min_value:
        color_range[0] = min_value
    if max_value:
        color_range[1] = max_value

    # Handle the different formats that colormap can take
    if colormap:
        if isinstance(colormap, str):
            colormap = plt.get_cmap(colormap)  # Convert to Colormap object
        if isinstance(colormap, Colormap):
            # Convert to the format of colormap required by k3d.volume
            attr_vals = np.linspace(0.0, 1.0, num=colormap.N)
            rgb_vals = colormap(np.arange(0, colormap.N))[:, :3]
            colormap = np.column_stack((attr_vals, rgb_vals)).tolist()

    # Default k3d.volume settings
    interpolation = True

    if constant_opacity:
        log.warning(
            'Deprecation warning: Keyword argument "constant_opacity" is deprecated and will be removed next release. Instead use opacity_function="constant".'
        )
        # without these settings, the plot will look bad when colormap is created with qim3d.viz.colormaps.objects
        opacity_function = [0.0, float(constant_opacity), 1.0, float(constant_opacity)]
        interpolation = False
    else:
        if opacity_function == 'constant':
            # without these settings, the plot will look bad when colormap is created with qim3d.viz.colormaps.objects
            opacity_function = [0.0, float(True), 1.0, float(True)]
            interpolation = False
        elif opacity_function is None:
            opacity_function = []

    # Create the volume plot
    plt_volume = k3d.volume(
        volume,
        bounds=(
            [0, volume.shape[2], 0, volume.shape[1], 0, volume.shape[0]]
            if aspectmode.lower() == 'data'
            else None
        ),
        colormap=colormap,
        samples=samples,
        color_range=color_range,
        opacity_function=opacity_function,
        interpolation=interpolation,
    )
    plot = k3d.plot(grid_visible=grid_visible, **kwargs)
    plot += plt_volume
    plot.camera_mode = camera_mode
    if save:
        # Save html to disk
        with open(str(save), 'w', encoding='utf-8') as fp:
            fp.write(plot.get_snapshot())

    if show:
        plot.display()
    else:
        return plot

qim3d.viz.itk_vtk

itk_vtk(
    filename=None,
    open_browser=True,
    file_server_port=8042,
    viewer_port=3000,
)

Launches the ITK-VTK Viewer in a web browser to visualize 3D data.

Starts a local file server and opens a dedicated visualization window in your default web browser. This function is particularly effective for viewing OME-Zarr stores and other large datasets that benefit from on-demand loading. If the viewer is not found, it prompts to handle the installation automatically.

Key Features:

  • Web-Based: Runs the visualization in a browser tab.
  • Large Data Support: Efficiently streams data, making it ideal for large segmentation masks or multi-scale pyramids.
  • Auto-Configuration: Manages local ports and installation dependencies automatically.

Parameters:

Name Type Description Default
filename str or PathLike

Path to the file or OME-Zarr store to be visualized.

None
open_browser bool

If True, automatically opens the visualization in a new tab.

True
file_server_port int

The port number for the local file server hosting the data.

8042
viewer_port int

The port number for the ITK-VTK viewer application.

3000
Source code in qim3d/viz/itk_vtk_viewer/run.py
def itk_vtk(
    filename: str = None,
    open_browser: bool = True,
    file_server_port: int = 8042,
    viewer_port: int = 3000,
):
    """
    Launches the ITK-VTK Viewer in a web browser to visualize 3D data.

    Starts a local file server and opens a dedicated visualization window in your default web browser. This function is particularly effective for viewing OME-Zarr stores and other large datasets that benefit from on-demand loading. If the viewer is not found, it prompts to handle the installation automatically.

    **Key Features:**

    * **Web-Based:** Runs the visualization in a browser tab.
    * **Large Data Support:** Efficiently streams data, making it ideal for large segmentation masks or multi-scale pyramids.
    * **Auto-Configuration:** Manages local ports and installation dependencies automatically.

    Args:
        filename (str or PathLike, optional): Path to the file or OME-Zarr store to be visualized.
        open_browser (bool, optional): If `True`, automatically opens the visualization in a new tab.
        file_server_port (int, optional): The port number for the local file server hosting the data.
        viewer_port (int, optional): The port number for the ITK-VTK viewer application.
    """

    try:
        try_opening_itk_vtk(
            filename,
            open_browser=open_browser,
            file_server_port=file_server_port,
            viewer_port=viewer_port,
        )

    except NotInstalledError:
        message = "Itk-vtk-viewer is not installed or qim3d can not find it.\nYou can either:\n\to  Use 'qim3d viz SOURCE -m k3d' to display data using different method\n\to  Install itk-vtk-viewer yourself following https://kitware.github.io/itk-vtk-viewer/docs/cli.html#Installation\n\to  Let qim3d install itk-vtk-viewer now (it will also install node.js in qim3d library)\nDo you want qim3d to install itk-vtk-viewer now?"
        print(message)
        answer = input('[Y/n]:')
        if answer in 'Yy':
            Installer().install()
            try_opening_itk_vtk(
                filename,
                open_browser=open_browser,
                file_server_port=file_server_port,
                viewer_port=viewer_port,
            )

qim3d.viz.mesh

mesh(
    mesh,
    wireframe=False,
    show_edges=True,
    show=True,
    save_screenshot='',
    export_html='',
    explode=0,
    smooth_shading=False,
    face_color='#cccccc',
    edge_color='#993333',
    **kwargs,
)

Visualize a 3D mesh using pygel3d or pyvista. If you need more advanced tools, use pyvista directly.

Parameters:

Name Type Description Default
mesh Manifold

The input mesh object.

required
wireframe bool

If True, displays the mesh as a wireframe. Defaults to False.

False
show_edges bool

If True, shows edges of the mesh. Fefaults to True.

True
show bool

If True, displays the visualization inline, useful for multiple plots. Works only with backend pyvista. Defaults to True.

True
save_screenshot str

If True, saves the visualization as an png file. The string is interpreted as the file path where the screenshot will be saved. Works only with the backend pyvista. Defaults to ''.

''
export_html str

If True, saves the visualization as an html file. The string is interpreted as the file path where the scene will be saved. Works only with the backend pyvista. Defaults to ''.

''
explode int

Only works when mesh is qim3d.mesh.VolumeMesh. Defines how spread are the tetrahedrons. If 0, the volume us intact. Defaults to 1.

0
smooth_shading bool

Smooths out edges. Only works with `pyvista'. Defaults to False.

False
face_color str

Face color of the mesh. Onyl works with pyvista. Doesn't work with `wireframe = True'. Defaults to '#cccccc'.

'#cccccc'
edge_color str

Edge color of the mesh. Only works with pyvista. Defaults to '#993333'.

'#993333'
**kwargs Any

Additional keyword arguments specific to the chosen backend: - pyvista kwargs: Arguments that customize the pyvista visualization. - pygel3d.display kwargs: Arguments that customize the pygel3d.display visualization.

{}

Returns:

Name Type Description
None None

The function displays the mesh but does not return a plot object.

Example

import qim3d

# Generate a 3D blob
synthetic_blob = qim3d.generate.volume()

# Convert the 3D numpy array to a Pygel3D mesh object
mesh = qim3d.mesh.from_volume(synthetic_blob, mesh_precision=0.5)

# Visualize the generated mesh
qim3d.viz.mesh(mesh)
pygel3d_visualization

qim3d.viz.mesh(mesh, backend='k3d', wireframe=False, flat_shading=False)
k3d_visualization

Source code in qim3d/viz/_mesh.py
def mesh(
    mesh: pygel3d.hmesh.Manifold | SurfaceMesh | VolumeMesh,
    wireframe: bool = False,
    show_edges: bool = True,
    show: bool = True,
    save_screenshot: str = '',
    export_html:str = '',
    explode: int = 0,
    smooth_shading: bool = False,
    face_color = '#cccccc',
    edge_color = '#993333',
    **kwargs,
) ->  None:
    """
    Visualize a 3D mesh using `pygel3d` or `pyvista`. If you need more advanced tools, use pyvista directly.

    Args:
        mesh (pygel3d.hmesh.Manifold): The input mesh object.
        wireframe (bool, optional): If True, displays the mesh as a wireframe. Defaults to False.
        show_edges (bool, optional): If True, shows edges of the mesh. Fefaults to True.
        show (bool, optional): If True, displays the visualization inline, useful for multiple plots.
            Works only with backend `pyvista`. Defaults to True.
        save_screenshot (str, optional): If True, saves the visualization as an `png` file.
            The string is interpreted as the file path where the screenshot will 
            be saved. Works only with the backend `pyvista`. Defaults to ''.
        export_html (str, optional): If True, saves the visualization as an `html` file.
            The string is interpreted as the file path where the scene will 
            be saved. Works only with the backend `pyvista`. Defaults to ''.
        explode (int, optional): Only works when mesh is qim3d.mesh.VolumeMesh.
            Defines how spread are the tetrahedrons. If 0, the volume us intact.
            Defaults to 1.
        smooth_shading (bool, optional): Smooths out edges. Only works with `pyvista'.
            Defaults to False.
        face_color (str, optional): Face color of the mesh. Onyl works with `pyvista`.
            Doesn't work with `wireframe = True'. Defaults to '#cccccc'.
        edge_color (str, optional): Edge color of the mesh. Only works with `pyvista`.
            Defaults to '#993333'.
        **kwargs (Any): Additional keyword arguments specific to the chosen backend:
            - `pyvista` kwargs: Arguments that customize the [`pyvista`](https://docs.pyvista.org/api/plotting) visualization.
            - `pygel3d.display` kwargs: Arguments that customize the [`pygel3d.display`](https://www2.compute.dtu.dk/projects/GEL/PyGEL/pygel3d/jupyter_display.html#display) visualization.

    Returns:
        None: The function displays the mesh but does not return a plot object.


    Example:
        ```python
        import qim3d

        # Generate a 3D blob
        synthetic_blob = qim3d.generate.volume()

        # Convert the 3D numpy array to a Pygel3D mesh object
        mesh = qim3d.mesh.from_volume(synthetic_blob, mesh_precision=0.5)

        # Visualize the generated mesh
        qim3d.viz.mesh(mesh)
        ```
        ![pygel3d_visualization](../../assets/screenshots/viz-pygel_mesh.png)

        ```python
        qim3d.viz.mesh(mesh, backend='k3d', wireframe=False, flat_shading=False)
        ```
        [k3d_visualization](../../assets/screenshots/sphere.html)
        <div class="scene">
            <iframe src="http://127.0.0.1:8000/qim3d/assets/screenshots/sphere.html" width="100%" height="500" frameborder="0"></iframe>
        </div>


    """

    if isinstance(mesh, (VolumeMesh, SurfaceMesh)):
        plotter = pv.Plotter()

        if isinstance(mesh, VolumeMesh):
            mesh = mesh.explode(explode)

        if wireframe:
            kwargs['style'] =  'wireframe'
        plotter.add_mesh(mesh, 
                         show_edges = show_edges, 
                         smooth_shading = smooth_shading,
                         show_scalar_bar=False,
                         color = face_color,
                         edge_color=edge_color, 
                         **kwargs)

        if show:
            plotter.show()

        if save_screenshot:
            if not save_screenshot.endswith('png'):
                save_screenshot = save_screenshot + '.png'
            plotter.screenshot(save_screenshot)

        if export_html:
            if not export_html.endswith('.html'):
                export_html = export_html + '.html'
            plotter.export_html(export_html)

        return

    if isinstance(mesh, pygel3d.hmesh.Manifold):
        if len(mesh.vertices()) > 100000:
            msg = f'The mesh has {len(mesh.vertices())} vertices, visualization may be slow. Consider using a smaller <mesh_precision> when computing the mesh.'
            log.info(msg)

        jd.set_export_mode(True)
        valid_pygel_kwargs = {k: v for k, v in kwargs.items() if k in ['smooth', 'data']}
        return jd.display(mesh, wireframe=show_edges, **valid_pygel_kwargs)

qim3d.viz.iso_surface

iso_surface(volume, colormap='Magma')

Creates an interactive tool to visualize 3D iso-surfaces (surfaces of constant value).

Generates a GUI to extract and render 3D contours from the volume in real-time. This is useful for finding specific intensity boundaries, visualizing segmentation masks, or exploring the shape of objects defined by a specific threshold. It uses Plotly for interaction and includes controls for resolution and transparency.

Key Features:

  • Interactive Thresholding: Adjust the iso-value dynamically to see how the surface changes.
  • Performance Control: Adjustable resolution slider to balance between mesh quality and rendering speed.
  • Visual Styles: Supports wireframe mode, transparency, and various colormaps.

Parameters:

Name Type Description Default
volume ndarray

The 3D input volume to be visualized.

required
colormap str

The initial color map name (e.g., 'Magma', 'Viridis'). Can be changed in the GUI.

'Magma'
Example

import qim3d

vol = qim3d.generate.volume(noise_scale=0.020)
qim3d.viz.iso_surface(vol)
volume_comparison

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def iso_surface(volume: np.ndarray, colormap: str = 'Magma') -> None:
    """
    Creates an interactive tool to visualize 3D iso-surfaces (surfaces of constant value).

    Generates a GUI to extract and render 3D contours from the volume in real-time. This is useful for finding specific intensity boundaries, visualizing segmentation masks, or exploring the shape of objects defined by a specific threshold. It uses `Plotly` for interaction and includes controls for resolution and transparency.

    **Key Features:**

    * **Interactive Thresholding:** Adjust the iso-value dynamically to see how the surface changes.
    * **Performance Control:** Adjustable resolution slider to balance between mesh quality and rendering speed.
    * **Visual Styles:** Supports wireframe mode, transparency, and various colormaps.

    Args:
        volume (numpy.ndarray): The 3D input volume to be visualized.
        colormap (str, optional): The initial color map name (e.g., 'Magma', 'Viridis'). Can be changed in the GUI.

    Example:
        ```python
        import qim3d

        vol = qim3d.generate.volume(noise_scale=0.020)
        qim3d.viz.iso_surface(vol)
        ```
        ![volume_comparison](../../assets/screenshots/iso_surface.gif)
    """
    IsoSurface(volume, colormap)

qim3d.viz.export_rotation

export_rotation(
    path,
    volume,
    degrees=360,
    n_frames=180,
    fps=30,
    image_size=(256, 256),
    colormap='magma',
    camera_height=2.0,
    camera_distance='auto',
    camera_focus='center',
    show=False,
)

Exports a 360-degree turntable animation of the volume to a video or GIF.

Generates a spinning orbit visualization of the 3D data, perfect for presentations, reports, or sharing results on the web. It renders the volume from a rotating camera perspective and saves the output as a movie file (.mp4, .webm, .avi) or an animated .gif.

Key Features:

  • Presentation Ready: Creates smooth, professional animations of your data.
  • Flexible Output: Supports common video formats and high-quality GIFs.
  • Customizable Camera: Control the height, distance, and focus point of the rotation.

Parameters:

Name Type Description Default
path str

The destination file path. Must end with .gif, .avi, .mp4, or .webm. If no extension is provided, defaults to .gif.

required
volume ndarray

The 3D input volume to be animated.

required
degrees int

Total rotation angle in degrees (e.g., 360 for a full spin).

360
n_frames int

Total number of frames to generate. Higher values create smoother/slower animations at fixed FPS.

180
fps int

Frames per second. Controls the playback speed.

30
image_size tuple[int, int] or None

Resolution (width, height) of the output frames.

(256, 256)
colormap str

Matplotlib colormap name for the volume rendering.

'magma'
camera_height float

Vertical position of the camera relative to the volume's Z-axis height.

2.0
camera_distance float or str

Distance from the camera to the focus point.

  • str: Use 'auto' to automatically calculate a fitting distance.
  • float: Specific distance in voxel units.
'auto'
camera_focus list or str

The point the camera rotates around.

  • str: Use 'center' to rotate around the volume center.
  • list: A list of 3 integers [z, y, x] specifying the voxel coordinate.
'center'
show bool

If True, displays the generated animation inline in the notebook.

False

Raises:

Type Description
TypeError

If camera_focus or camera_distance is invalid.

ValueError

If path contains an unsupported file extension.

Example

Creation of .gif file with default parameters of a generated volume.

import qim3d
vol = qim3d.generate.volume()

qim3d.viz.export_rotation('test.gif', vol, show=True)
export_rotation_defaults

Example

Creation of a .webm file with specified parameters of a generated volume in the shape of a tube.

import qim3d

vol = qim3d.generate.volume(shape='tube')

qim3d.viz.export_rotation('test.webm', vol,
                          degrees = 360,
                          n_frames = 120,
                          fps = 30,
                          image_size = (512,512),
                          camera_height = 3.0,
                          camera_distance = 'auto',
                          camera_focus = 'center',
                          show = True)
export_rotation_video

Source code in qim3d/viz/_data_exploration.py
def export_rotation(
    path: str,
    volume: np.ndarray,
    degrees: int = 360,
    n_frames: int = 180,
    fps: int = 30,
    image_size: tuple[int, int] | None = (256, 256),
    colormap: str = 'magma',
    camera_height: float = 2.0,
    camera_distance: float | str = 'auto',
    camera_focus: list | str = 'center',
    show: bool = False,
) -> None:
    """
    Exports a 360-degree turntable animation of the volume to a video or GIF.

    Generates a spinning orbit visualization of the 3D data, perfect for presentations, reports, or sharing results on the web. It renders the volume from a rotating camera perspective and saves the output as a movie file (.mp4, .webm, .avi) or an animated .gif.

    **Key Features:**

    * **Presentation Ready:** Creates smooth, professional animations of your data.
    * **Flexible Output:** Supports common video formats and high-quality GIFs.
    * **Customizable Camera:** Control the height, distance, and focus point of the rotation.

    Args:
        path (str): The destination file path. Must end with .gif, .avi, .mp4, or .webm. If no extension is provided, defaults to .gif.
        volume (numpy.ndarray): The 3D input volume to be animated.
        degrees (int, optional): Total rotation angle in degrees (e.g., 360 for a full spin).
        n_frames (int, optional): Total number of frames to generate. Higher values create smoother/slower animations at fixed FPS.
        fps (int, optional): Frames per second. Controls the playback speed.
        image_size (tuple[int, int] or None, optional): Resolution (width, height) of the output frames.
        colormap (str, optional): Matplotlib colormap name for the volume rendering.
        camera_height (float, optional): Vertical position of the camera relative to the volume's Z-axis height.
        camera_distance (float or str, optional): Distance from the camera to the focus point.

            * **str**: Use `'auto'` to automatically calculate a fitting distance.
            * **float**: Specific distance in voxel units.

        camera_focus (list or str, optional): The point the camera rotates around.

            * **str**: Use `'center'` to rotate around the volume center.
            * **list**: A list of 3 integers `[z, y, x]` specifying the voxel coordinate.

        show (bool, optional): If `True`, displays the generated animation inline in the notebook.

    Raises:
        TypeError: If `camera_focus` or `camera_distance` is invalid.
        ValueError: If `path` contains an unsupported file extension.

    Example:
        Creation of .gif file with default parameters of a generated volume.
        ```python
        import qim3d
        vol = qim3d.generate.volume()

        qim3d.viz.export_rotation('test.gif', vol, show=True)
        ```
        ![export_rotation_defaults](../../assets/screenshots/export_rotation_defaults.gif)

    Example:
        Creation of a .webm file with specified parameters of a generated volume in the shape of a tube.
        ```python
        import qim3d

        vol = qim3d.generate.volume(shape='tube')

        qim3d.viz.export_rotation('test.webm', vol,
                                  degrees = 360,
                                  n_frames = 120,
                                  fps = 30,
                                  image_size = (512,512),
                                  camera_height = 3.0,
                                  camera_distance = 'auto',
                                  camera_focus = 'center',
                                  show = True)
        ```
        ![export_rotation_video](../../assets/screenshots/export_rotation_video.gif)
    """
    if not (
        camera_focus == 'center'
        or (
            isinstance(camera_focus, list | np.ndarray)
            and not isinstance(camera_focus, str)
            and len(camera_focus) == 3
        )
    ):
        msg = f'Value "{camera_focus}" for camera focus is invalid. Use "center" or a list of three values.'
        raise TypeError(msg)
    if not (isinstance(camera_distance, float) or camera_distance == 'auto'):
        msg = f'Value "{camera_distance}" for camera distance is invalid. Use "auto" or a float value.'
        raise TypeError(msg)

    if Path(path).suffix == '':
        print(f'Input path: "{path}" does not have a filetype. Defaulting to .gif.')
        path += '.gif'

    # Handle img in (xyz) instead of (zyx) (due to rendering issues with the up-vector, ensure that z=y, such that we now have (x,z,y))
    vol = np.transpose(volume, (2, 0, 1))

    # Create a uniform grid
    grid = pv.ImageData()
    grid.dimensions = np.array(vol.shape) + 1  # PyVista dims are +1 from volume shape
    grid.spacing = (1, 1, 1)
    grid.origin = (0, 0, 0)
    grid.cell_data['values'] = vol.flatten(order='F')  # Fortran order

    # Initialize plotter
    plotter = pv.Plotter(off_screen=True)
    plotter.add_volume(grid, opacity='linear', cmap=colormap)
    plotter.remove_scalar_bar()  # Remove colorbar

    frames = []
    camera_height = vol.shape[1] * camera_height

    if camera_distance == 'auto':
        bounds = np.array(plotter.bounds)  # (xmin, xmax, ymin, ymax, zmin, zmax)
        diag = np.linalg.norm(
            [bounds[1] - bounds[0], bounds[3] - bounds[2], bounds[5] - bounds[4]]
        )
        camera_distance = diag * 2.0

    if camera_focus == 'center':
        _, center, _ = plotter.camera_position
    else:
        center = camera_focus

    center = np.array(center)

    angle_per_frame = degrees / n_frames
    radians_per_frame = np.radians(angle_per_frame)

    # Set up orbit radius and fixed up
    radius = camera_distance
    fixed_up = [0, 1, 0]
    for i in tqdm(range(n_frames), desc='Rendering'):
        theta = radians_per_frame * i
        x = radius * np.sin(theta)
        z = radius * np.cos(theta)
        y = camera_height  # fixed height

        eye = center + np.array([x, y, z])
        plotter.camera_position = [eye.tolist(), center.tolist(), fixed_up]

        plotter.render()
        img = plotter.screenshot(return_img=True, window_size=image_size)
        frames.append(img)

    if path[-4:] == '.gif':
        imageio.mimsave(path, frames, fps=fps, loop=0)

    elif path[-4:] == '.avi' or path[-4:] == '.mp4':
        writer = imageio.get_writer(path, fps=fps)
        for frame in frames:
            writer.append_data(frame)
        writer.close()

    elif path[-5:] == '.webm':
        writer = imageio.get_writer(
            path, fps=fps, codec='vp9', ffmpeg_params=['-crf', '32']
        )
        for frame in frames:
            writer.append_data(frame)
        writer.close()

    else:
        msg = 'Invalid file extension. Please use .gif, .avi, .mp4 or .webm'
        raise ValueError(msg)

    path = _get_save_path(path)
    log.info('File saved to ' + str(path.resolve()))

    if show:
        if path.suffix == '.gif':
            display(Image(filename=path))
        elif path.suffix in ['.avi', '.mp4', '.webm']:
            display(Video(filename=path, html_attributes='controls autoplay loop'))