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,
    color_map='magma',
    value_min=None,
    value_max=None,
    image_height=3,
    image_width=3,
    display_positions=False,
    interpolation=None,
    image_size=None,
    color_bar=None,
    mask=None,
    mask_alpha=0.4,
    mask_color_map='gray',
    default_position=0.5,
    **matplotlib_imshow_kwargs,
)

Interactive widget for visualizing slices of a 3D volume.

Parameters:

Name Type Description Default
volume ndarray

The 3D volume to be sliced.

required
slice_axis int

Specifies the axis, or dimension, along which to slice. Defaults to 0.

0
color_map str or LinearSegmentedColormap

Specifies the color map for the image. Defaults to 'magma'.

'magma'
value_min float

Together with value_max define the data range the colormap covers. By default colormap covers the full range. Defaults to None.

None
value_max float

Together with value_min define the data range the colormap covers. By default colormap covers the full range. Defaults to None

None
image_height int

Height of the figure. Defaults to 3.

3
image_width int

Width of the figure. Defaults to 3.

3
display_positions bool

If True, displays the position of the slices. Defaults to False.

False
interpolation str

Specifies the interpolation method for the image. Defaults to None.

None
image_size int

Size of the figure. If set, image_height and image_width are ignored. Defaults to None.

None
color_bar str

Controls the options for color bar. If None, no color bar is included. If 'volume', the color map range is constant for each slice. If 'slices', the color map range changes dynamically according to the slice. Defaults to None.

None
mask ndarray

Overlays the image with this segmentation mask. Defaults to None.

None
mask_alpha float

Sets the alpha of the overlaying mask. Defaults to 0.4.

0.4
mask_color_map str

Sets the color map of the overlaying mask. Defaults to 'gray'.

'gray'
default_position float | int

Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.

0.5
**matplotlib_imshow_kwargs Any

Additional keyword arguments to pass to the matplotlib.pyplot.imshow function.

{}

Returns:

Name Type Description
slicer_obj interactive

The interactive widget for visualizing slices of a 3D volume.

Example

import qim3d

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

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def slicer(
    volume: np.ndarray,
    slice_axis: int = 0,
    color_map: str = 'magma',
    value_min: float = None,
    value_max: float = None,
    image_height: int = 3,
    image_width: int = 3,
    display_positions: bool = False,
    interpolation: str | None = None,
    image_size: int = None,
    color_bar: str = None,
    mask:np.ndarray = None,
    mask_alpha:float = 0.4,
    mask_color_map = 'gray',
    default_position:float|int = 0.5,
    **matplotlib_imshow_kwargs,
) -> widgets.interactive:
    """
    Interactive widget for visualizing slices of a 3D volume.

    Args:
        volume (np.ndarray): The 3D volume to be sliced.
        slice_axis (int, optional): Specifies the axis, or dimension, along which to slice. Defaults to 0.
        color_map (str or matplotlib.colors.LinearSegmentedColormap, optional): Specifies the color map for the image. Defaults to 'magma'.
        value_min (float, optional): Together with value_max define the data range the colormap covers. By default colormap covers the full range. Defaults to None.
        value_max (float, optional): Together with value_min define the data range the colormap covers. By default colormap covers the full range. Defaults to None
        image_height (int, optional): Height of the figure. Defaults to 3.
        image_width (int, optional): Width of the figure. Defaults to 3.
        display_positions (bool, optional): If True, displays the position of the slices. Defaults to False.
        interpolation (str, optional): Specifies the interpolation method for the image. Defaults to None.
        image_size (int, optional): Size of the figure. If set, image_height and image_width are ignored. Defaults to None.
        color_bar (str, optional): Controls the options for color bar. If None, no color bar is included. If 'volume', the color map range is constant for each slice. If 'slices', the color map range changes dynamically according to the slice. Defaults to None.
        mask (np.ndarray, optional): Overlays the image with this segmentation mask. Defaults to None.
        mask_alpha (float, optional): Sets the alpha of the overlaying mask. Defaults to 0.4.
        mask_color_map (str, optional): Sets the color map of the overlaying mask. Defaults to 'gray'.
        default_position (float|int, optional): Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.
        **matplotlib_imshow_kwargs (Any): Additional keyword arguments to pass to the `matplotlib.pyplot.imshow` function.

    Returns:
        slicer_obj (widgets.interactive): The interactive widget for visualizing slices of a 3D volume.

    Example:
        ```python
        import qim3d

        vol = qim3d.examples.bone_128x128x128
        qim3d.viz.slicer(vol)
        ```
        ![viz slicer](../../assets/screenshots/viz-slicer.gif)

    """

    if image_size:
        image_height = image_size
        image_width = image_size

    color_bar_options = [None, 'slices', 'volume']
    if color_bar not in color_bar_options:
        msg = (
            f"Unrecognized value '{color_bar}' for parameter color_bar. "
            f'Expected one of {color_bar_options}.'
        )
        raise ValueError(msg)
    show_color_bar = color_bar is not None
    if color_bar == '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 color_bar == 'slices':
            dynamic_min = slice_mins[slice_positions]
            dynamic_max = slice_maxs[slice_positions]
        else:
            dynamic_min = value_min
            dynamic_max = value_max

        fig = slices_grid(
            volume,
            slice_axis=slice_axis,
            color_map=color_map,
            value_min=dynamic_min,
            value_max=dynamic_max,
            image_height=image_height,
            image_width=image_width,
            display_positions=display_positions,
            interpolation=interpolation,
            slice_positions=slice_positions,
            num_slices=1,
            display_figure=True,
            color_bar=show_color_bar,
            mask = mask,
            mask_alpha = mask_alpha,
            mask_color_map = mask_color_map,
            **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,
    num_slices=15,
    max_columns=5,
    color_map='magma',
    value_min=None,
    value_max=None,
    image_size=None,
    image_height=2,
    image_width=2,
    display_figure=False,
    display_positions=True,
    interpolation=None,
    color_bar=False,
    color_bar_style='small',
    mask=None,
    mask_alpha=0.4,
    mask_color_map='gray',
    **matplotlib_imshow_kwargs,
)

Displays one or several slices from a 3d volume.

By default if slice_positions is None, slices_grid plots num_slices linearly spaced slices. If slice_positions is given as a string or integer, slices_grid will plot an overview with num_slices figures around that position. If slice_positions is given as a list, num_slices will be ignored and the slices from slice_positions will be plotted.

Parameters:

Name Type Description Default
volume ndarray

The 3D volume to be sliced.

required
slice_axis int

Specifies the axis, or dimension, along which to slice. Defaults to 0.

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

One or several slicing levels. If None, linearly spaced slices will be displayed. Defaults to None.

None
num_slices int

Defines how many slices the user wants to be displayed. Defaults to 15.

15
max_columns int

The maximum number of columns to be plotted. Defaults to 5.

5
color_map str or LinearSegmentedColormap

Specifies the color map for the image. Defaults to "magma".

'magma'
value_min float

Together with value_max define the data range the colormap covers. By default colormap covers the full range. Defaults to None.

None
value_max float

Together with value_min define the data range the colormap covers. By default colormap covers the full range. Defaults to None

None
image_size int

Size of the figure. If set, image_height and image_width are ignored.

None
image_height int

Height of the figure.

2
image_width int

Width of the figure.

2
display_figure bool

If True, displays the plot (i.e. calls plt.show()). Defaults to False.

False
display_positions bool

If True, displays the position of the slices. Defaults to True.

True
interpolation str

Specifies the interpolation method for the image. Defaults to None.

None
color_bar bool

Adds a colorbar positioned in the top-right for the corresponding colormap and data range. Defaults to False.

False
color_bar_style str

Determines the style of the colorbar. Option 'small' is height of one image row. Option 'large' spans full height of image grid. Defaults to 'small'.

'small'
**matplotlib_imshow_kwargs Any

Additional keyword arguments to pass to the matplotlib.pyplot.imshow function.

{}

Returns:

Name Type Description
fig Figure

The figure with the slices from the 3d array.

Raises:

Type Description
ValueError

If the input is not a numpy.ndarray or da.core.Array.

ValueError

If the slice_axis to slice along is not a valid choice, i.e. not an integer between 0 and the number of dimensions of the volume minus 1.

ValueError

If the file or array is not a volume with at least 3 dimensions.

ValueError

If the position keyword argument is not a integer, list of integers or one of the following strings: "start", "mid" or "end".

ValueError

If the color_bar_style keyword argument is not one of the following strings: 'small' or 'large'.

Example

import qim3d

vol = qim3d.examples.shell_225x128x128
qim3d.viz.slices_grid(vol, num_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,
    num_slices: int = 15,
    max_columns: int = 5,
    color_map: str = 'magma',
    value_min: float = None,
    value_max: 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,
    color_bar: bool = False,
    color_bar_style: str = 'small',
    mask:np.ndarray = None,
    mask_alpha:float = 0.4,
    mask_color_map:str = 'gray',
    **matplotlib_imshow_kwargs,
) -> matplotlib.figure.Figure:
    """
    Displays one or several slices from a 3d volume.

    By default if `slice_positions` is None, slices_grid plots `num_slices` linearly spaced slices.
    If `slice_positions` is given as a string or integer, slices_grid will plot an overview with `num_slices` figures around that position.
    If `slice_positions` is given as a list, `num_slices` will be ignored and the slices from `slice_positions` will be plotted.

    Args:
        volume (np.ndarray): The 3D volume to be sliced.
        slice_axis (int, optional): Specifies the axis, or dimension, along which to slice. Defaults to 0.
        slice_positions (int or list[int] or str or None, optional): One or several slicing levels. If None, linearly spaced slices will be displayed. Defaults to None.
        num_slices (int, optional): Defines how many slices the user wants to be displayed. Defaults to 15.
        max_columns (int, optional): The maximum number of columns to be plotted. Defaults to 5.
        color_map (str or matplotlib.colors.LinearSegmentedColormap, optional): Specifies the color map for the image. Defaults to "magma".
        value_min (float, optional): Together with value_max define the data range the colormap covers. By default colormap covers the full range. Defaults to None.
        value_max (float, optional): Together with value_min define the data range the colormap covers. By default colormap covers the full range. Defaults to None
        image_size (int, optional): Size of the figure. If set, image_height and image_width are ignored.
        image_height (int, optional): Height of the figure.
        image_width (int, optional): Width of the figure.
        display_figure (bool, optional): If True, displays the plot (i.e. calls plt.show()). Defaults to False.
        display_positions (bool, optional): If True, displays the position of the slices. Defaults to True.
        interpolation (str, optional): Specifies the interpolation method for the image. Defaults to None.
        color_bar (bool, optional): Adds a colorbar positioned in the top-right for the corresponding colormap and data range. Defaults to False.
        color_bar_style (str, optional): Determines the style of the colorbar. Option 'small' is height of one image row. Option 'large' spans full height of image grid. Defaults to 'small'.
        **matplotlib_imshow_kwargs (Any): Additional keyword arguments to pass to the `matplotlib.pyplot.imshow` function.

    Returns:
        fig (matplotlib.figure.Figure): The figure with the slices from the 3d array.

    Raises:
        ValueError: If the input is not a numpy.ndarray or da.core.Array.
        ValueError: If the slice_axis to slice along is not a valid choice, i.e. not an integer between 0 and the number of dimensions of the volume minus 1.
        ValueError: If the file or array is not a volume with at least 3 dimensions.
        ValueError: If the `position` keyword argument is not a integer, list of integers or one of the following strings: "start", "mid" or "end".
        ValueError: If the color_bar_style keyword argument is not one of the following strings: 'small' or 'large'.

    Example:
        ```python
        import qim3d

        vol = qim3d.examples.shell_225x128x128
        qim3d.viz.slices_grid(vol, num_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.core.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)

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

    if isinstance(volume, da.core.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(color_map) == matplotlib.colors.LinearSegmentedColormap
        or color_map == 'segmentation'
    ):
        num_labels = volume.max()

        if color_map == 'segmentation':
            color_map = qim3d.viz.colormaps.segmentation(num_labels)
        # If value_min and value_max 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.
        value_min = 0
        value_max = 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, num_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, num_slices, n_total)
        elif slice_positions.lower() == 'mid':
            slice_idxs = _get_slice_range(n_total // 2, num_slices, n_total)
        elif slice_positions.lower() == 'end':
            slice_idxs = _get_slice_range(n_total - 1, num_slices, n_total)
    #  Position is an integer
    elif isinstance(slice_positions, int):
        slice_idxs = _get_slice_range(slice_positions, num_slices, n_total)
    # Position is a list of integers
    elif isinstance(slice_positions, list) and all(
        isinstance(idx, int) for idx in slice_positions
    ):
        slice_idxs = slice_positions
    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(num_slices / max_columns)
    ncols = min(num_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.core.Array):
        volume = volume.compute()

    if color_bar:
        # In this case, we want the vrange to be constant across the
        # slices, which makes them all comparable to a single color_bar.
        new_value_min = value_min if value_min is not None else np.min(volume)
        new_value_max = value_max if value_max 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 color_bar:
                    # If value_min 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_value_min = (
                        None
                        if (
                            isinstance(value_min, float | int)
                            and value_min > np.max(slice_img)
                        )
                        else value_min
                    )
                    new_value_max = (
                        None
                        if (
                            isinstance(value_max, float | int)
                            and value_max < np.min(slice_img)
                        )
                        else value_max
                    )

                ax.imshow(
                    slice_img,
                    cmap=color_map,
                    interpolation=interpolation,
                    vmin=new_value_min,
                    vmax=new_value_max,
                    **matplotlib_imshow_kwargs,
                )
                if slice_mask is not None:
                    ax.imshow(slice_mask, cmap = mask_color_map, 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 color_bar:
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', category=UserWarning)
            fig.tight_layout()

        norm = matplotlib.colors.Normalize(
            vmin=new_value_min, vmax=new_value_max, clip=True
        )
        mappable = matplotlib.cm.ScalarMappable(norm=norm, cmap=color_map)

        if color_bar_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
            color_bar_ax = fig.add_axes(
                [tr_pos.x1 + 0.05 / ncols, tr_pos.y0, 0.05 / ncols, tr_pos.height]
            )
            fig.colorbar(mappable=mappable, cax=color_bar_ax, orientation='vertical')
        elif color_bar_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
            color_bar_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=color_bar_ax, orientation='vertical')

    if display_figure:
        plt.show()

    plt.close()

    return fig

qim3d.viz.planes

planes(
    volume,
    color_map='magma',
    value_min=None,
    value_max=None,
)

Displays an interactive 3D widget for viewing orthogonal cross-sections through a volume.

Parameters:

Name Type Description Default
volume ndarray

The 3D volume of interest.

required
color_map str or Colormap

Specifies the matplotlib color map.

'magma'
value_min float

Together with value_max define the data range the colormap covers. By default colormap covers the full range.

None
value_max float

Together with value_min define the data range the colormap covers. By default colormap covers the full range.

None

Returns:

Type Description
None

None

Example

import qim3d

vol = qim3d.examples.shell_225x128x128
qim3d.viz.planes(vol)
viz planes

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def planes(
    volume: np.ndarray,
    color_map: str | matplotlib.colors.Colormap = 'magma',
    value_min: float = None,
    value_max: float = None,
) -> None:
    """
    Displays an interactive 3D widget for viewing orthogonal cross-sections through a volume.

    Args:
        volume (np.ndarray): The 3D volume of interest.
        color_map (str or matplotlib.colors.Colormap, optional): Specifies the matplotlib color map.
        value_min (float, optional): Together with value_max define the data range the colormap covers. By default colormap covers the full range.
        value_max (float, optional): Together with value_min define the data range the colormap covers. By default colormap covers the full range.

    Returns:
        None

    Example:
        ```python
        import qim3d

        vol = qim3d.examples.shell_225x128x128
        qim3d.viz.planes(vol)
        ```
        ![viz planes](../../assets/screenshots/viz-planes.gif)

    """
    VolumePlaneSlicer(
        volume=volume, color_map=color_map, color_range=[value_min, value_max]
    ).show()

qim3d.viz.slicer_orthogonal

slicer_orthogonal(
    volume,
    color_map='magma',
    value_min=None,
    value_max=None,
    image_height=3,
    image_width=3,
    display_positions=False,
    interpolation=None,
    image_size=None,
    color_bar=None,
    mask=None,
    mask_alpha=0.4,
    mask_color_map='gray',
    default_z=0.5,
    default_y=0.5,
    default_x=0.5,
)

Interactive widget for visualizing orthogonal slices of a 3D volume.

Parameters:

Name Type Description Default
volume ndarray

The 3D volume to be sliced.

required
color_map str or LinearSegmentedColormap

Specifies the color map for the image. Defaults to "magma".

'magma'
value_min float

Together with value_max define the data range the colormap covers. By default colormap covers the full range. Defaults to None.

None
value_max float

Together with value_min define the data range the colormap covers. By default colormap covers the full range. Defaults to None

None
image_height int

Height of the figure.

3
image_width int

Width of the figure.

3
display_positions bool

If True, displays the position of the slices. Defaults to False.

False
interpolation str

Specifies the interpolation method for the image. Defaults to None.

None
image_size int

Size of the figure. If set, image_height and image_width are ignored. Defaults to None.

None
color_bar str

Controls the options for color bar. If None, no color bar is included. If 'volume', the color map range is constant for each slice. If 'slices', the color map range changes dynamically according to the slice. Defaults to None.

None
mask ndarray

Overlays the image with this segmentation mask. Defaults to None.

None
mask_alpha float

Sets the alpha of the overlaying mask. Defaults to 0.4.

0.4
mask_color_map str

Sets the color map of the overlaying mask. Defaults to 'gray'.

'gray'
default_x float | int

Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.

0.5
default_y float | int

Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.

0.5
default_z float | int

Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.

0.5

Returns:

Name Type Description
slicer_orthogonal_obj HBox

The interactive widget for visualizing orthogonal slices of a 3D volume.

Example

import qim3d

vol = qim3d.examples.fly_150x256x256
qim3d.viz.slicer_orthogonal(vol, color_map="magma")
viz slicer_orthogonal

Source code in qim3d/viz/_data_exploration.py
@coarseness('volume')
def slicer_orthogonal(
    volume: np.ndarray,
    color_map: str = 'magma',
    value_min: float = None,
    value_max: float = None,
    image_height: int = 3,
    image_width: int = 3,
    display_positions: bool = False,
    interpolation: str | None = None,
    image_size: int = None,
    color_bar:str = None,
    mask:np.ndarray = None,
    mask_alpha:float = 0.4,
    mask_color_map:str = 'gray',
    default_z:float|int = 0.5,
    default_y:float|int = 0.5,
    default_x:float|int = 0.5,
) -> widgets.interactive:
    """
    Interactive widget for visualizing orthogonal slices of a 3D volume.

    Args:
        volume (np.ndarray): The 3D volume to be sliced.
        color_map (str or matplotlib.colors.LinearSegmentedColormap, optional): Specifies the color map for the image. Defaults to "magma".
        value_min (float, optional): Together with value_max define the data range the colormap covers. By default colormap covers the full range. Defaults to None.
        value_max (float, optional): Together with value_min define the data range the colormap covers. By default colormap covers the full range. Defaults to None
        image_height (int, optional): Height of the figure.
        image_width (int, optional): Width of the figure.
        display_positions (bool, optional): If True, displays the position of the slices. Defaults to False.
        interpolation (str, optional): Specifies the interpolation method for the image. Defaults to None.
        image_size (int, optional): Size of the figure. If set, image_height and image_width are ignored. Defaults to None.
        color_bar (str, optional): Controls the options for color bar. If None, no color bar is included. If 'volume', the color map range is constant for each slice. If 'slices', the color map range changes dynamically according to the slice. Defaults to None.
        mask (np.ndarray, optional): Overlays the image with this segmentation mask. Defaults to None.
        mask_alpha (float, optional): Sets the alpha of the overlaying mask. Defaults to 0.4.
        mask_color_map (str, optional): Sets the color map of the overlaying mask. Defaults to 'gray'.
        default_x (float|int, optional): Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.
        default_y (float|int, optional): Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.
        default_z (float|int, optional): Set the x slicer to this slice after reload. If float, it should be between 0 and 1 to set position relative to shape. If int, it sets the exact slice. Defaults to 0.5.

    Returns:
        slicer_orthogonal_obj (widgets.HBox): The interactive widget for visualizing orthogonal slices of a 3D volume.

    Example:
        ```python
        import qim3d

        vol = qim3d.examples.fly_150x256x256
        qim3d.viz.slicer_orthogonal(vol, color_map="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,
        color_map=color_map,
        value_min=value_min,
        value_max=value_max,
        image_height=image_height,
        image_width=image_width,
        display_positions=display_positions,
        interpolation=interpolation,
        color_bar=color_bar,
        mask = mask,
        mask_alpha = mask_alpha,
        mask_color_map = mask_color_map,
        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,
    color_map='magma',
    constant_opacity=False,
    opacity_function=None,
    vmin=None,
    vmax=None,
    samples='auto',
    max_voxels=256**3,
    data_type='scaled_float16',
    camera_mode='orbit',
    **kwargs,
)

Visualizes a 3D volume using volumetric rendering.

Parameters:

Name Type Description Default
volume ndarray

The input 3D image data. It should be a 3D numpy array.

required
aspectmode str

Determines the proportions of the scene's axes. Defaults to "data". If 'data', the axes are drawn in proportion with the axes' ranges. If 'cube', the axes are drawn as a cube, regardless of the axes' ranges.

'data'
show bool

If True, displays the visualization inline. Defaults to True.

True
save bool or str

If True, saves the visualization as an HTML file. If a string is provided, it's interpreted as the file path where the HTML file will be saved. Defaults to False.

False
grid_visible bool

If True, the grid is visible in the plot. Defaults to False.

False
color_map str or Colormap or list

The color map to be used for the volume rendering. If a string is passed, it should be a matplotlib colormap name. Defaults to 'magma'.

'magma'
constant_opacity bool

Set to True if doing an object label visualization with a corresponding color_map; otherwise, the plot may appear poorly. Defaults to False.

False
opacity_function str or list

Applies an opacity function to the plot, enabling custom values for opaqueness. Set to True if doing an object label visualization with a corresponding color_map; otherwise, the plot may appear poorly. Defaults to [].

None
vmin float or None

Together with vmax defines the data range the colormap covers. By default colormap covers the full range. Defaults to None.

None
vmax float or None

Together with vmin defines the data range the colormap covers. By default colormap covers the full range. Defaults to None

None
samples int or auto

The number of samples to be used for the volume rendering in k3d. Input 'auto' for auto selection. Defaults to 'auto'. Lower values will render faster but with lower quality.

'auto'
max_voxels int

Defaults to 256^3.

256 ** 3
data_type str

Default to 'scaled_float16'.

'scaled_float16'
camera_mode str

Camera interaction mode, being 'orbit', 'trackball' or 'fly'. Defaults to 'orbit'.

'orbit'
**kwargs Any

Additional keyword arguments to be passed to the k3d.plot function.

{}

Returns:

Name Type Description
plot plot

If show=False, returns the K3D plot object.

Raises:

Type Description
ValueError

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

Tip

The function can be used for object label visualization using a color_map 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 a plot to an HTML file:

import qim3d
vol = qim3d.examples.bone_128x128x128
plot = qim3d.viz.volumetric(vol, show=False, save="plot.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,
    color_map: str = 'magma',
    constant_opacity: bool = False,
    opacity_function: str | list = None,
    vmin: float | None = None,
    vmax: 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:
    """
    Visualizes a 3D volume using volumetric rendering.

    Args:
        volume (numpy.ndarray): The input 3D image data. It should be a 3D numpy array.
        aspectmode (str, optional): Determines the proportions of the scene's axes. Defaults to "data".
            If `'data'`, the axes are drawn in proportion with the axes' ranges.
            If `'cube'`, the axes are drawn as a cube, regardless of the axes' ranges.
        show (bool, optional): If True, displays the visualization inline. Defaults to True.
        save (bool or str, optional): If True, saves the visualization as an HTML file.
            If a string is provided, it's interpreted as the file path where the HTML
            file will be saved. Defaults to False.
        grid_visible (bool, optional): If True, the grid is visible in the plot. Defaults to False.
        color_map (str or matplotlib.colors.Colormap or list, optional): The color map to be used for the volume rendering. If a string is passed, it should be a matplotlib colormap name. Defaults to 'magma'.
        constant_opacity (bool): Set to True if doing an object label visualization with a corresponding color_map; otherwise, the plot may appear poorly. Defaults to False.
        opacity_function (str or list, optional): Applies an opacity function to the plot, enabling custom values for opaqueness. Set to True if doing an object label visualization with a corresponding color_map; otherwise, the plot may appear poorly. Defaults to [].
        vmin (float or None, optional): Together with vmax defines the data range the colormap covers. By default colormap covers the full range. Defaults to None.
        vmax (float or None, optional): Together with vmin defines the data range the colormap covers. By default colormap covers the full range. Defaults to None
        samples (int or 'auto', optional): The number of samples to be used for the volume rendering in k3d. Input 'auto' for auto selection. Defaults to 'auto'.
            Lower values will render faster but with lower quality.
        max_voxels (int, optional): Defaults to 256^3.
        data_type (str, optional): Default to 'scaled_float16'.
        camera_mode (str, optional): Camera interaction mode, being 'orbit', 'trackball' or 'fly'. Defaults to 'orbit'.
        **kwargs (Any): Additional keyword arguments to be passed to the `k3d.plot` function.

    Returns:
        plot (k3d.plot): If `show=False`, returns the K3D plot object.

    Raises:
        ValueError: If `aspectmode` is not `'data'` or `'cube'`.

    Tip:
        The function can be used for object label visualization using a `color_map` 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 a plot to an HTML file:

        ```python
        import qim3d
        vol = qim3d.examples.bone_128x128x128
        plot = qim3d.viz.volumetric(vol, show=False, save="plot.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 vmin:
        color_range[0] = vmin
    if vmax:
        color_range[1] = vmax

    # Handle the different formats that color_map can take
    if color_map:
        if isinstance(color_map, str):
            color_map = plt.get_cmap(color_map)  # Convert to Colormap object
        if isinstance(color_map, Colormap):
            # Convert to the format of color_map required by k3d.volume
            attr_vals = np.linspace(0.0, 1.0, num=color_map.N)
            rgb_vals = color_map(np.arange(0, color_map.N))[:, :3]
            color_map = 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 color_map 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 color_map 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
        ),
        color_map=color_map,
        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,
)

Command to run in cli/init.py. Tries to run the vizualization, if that fails, asks the user to install it. This function is needed here so we don't have to import NotInstalledError and Installer, which exposes these to user.

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,
):
    """
    Command to run in cli/__init__.py. Tries to run the vizualization,
    if that fails, asks the user to install it. This function is needed
    here so we don't have to import NotInstalledError and Installer,
    which exposes these to user.
    """

    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,
    backend='pygel3d',
    wireframe=True,
    flat_shading=True,
    grid_visible=False,
    show=True,
    save=False,
    **kwargs,
)

Visualize a 3D mesh using pygel3d or k3d. The visualization with the pygel3d backend provides higher-quality rendering, but it may take more time compared to using the k3d backend.

Parameters:

Name Type Description Default
mesh Manifold

The input mesh object.

required
backend str

The visualization backend to use. Choose between pygel3d (default) and k3d.

'pygel3d'
wireframe bool

If True, displays the mesh as a wireframe. Works both with backend pygel3d and k3d. Defaults to True.

True
flat_shading bool

If True, applies flat shading to the mesh. Works only with backend k3d. Defaults to True.

True
grid_visible bool

If True, shows a grid in the visualization. Works only with backend k3d. Defaults to False.

False
show bool

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

True
save bool or str

If True, saves the visualization as an HTML file. If a string is provided, it's interpreted as the file path where the HTML file will be saved. Works only with the backend k3d. Defaults to False.

False
**kwargs Any

Additional keyword arguments specific to the chosen backend:

  • k3d.plot kwargs: Arguments that customize the k3d.plot visualization.

  • pygel3d.display kwargs: Arguments that customize the pygel3d.display visualization.

{}

Returns:

Type Description
Plot | FigureWidget | None

k3d.Plot or None:

  • If backend="k3d", returns a k3d.Plot object.
  • If backend="pygel3d", the function displays the mesh but does not return a plot object.

Raises:

Type Description
ValueError

If backend is not pygel3d or k3d.

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/_k3d.py
def mesh(
    mesh: pygel3d.hmesh.Manifold,
    backend: str = 'pygel3d',
    wireframe: bool = True,
    flat_shading: bool = True,
    grid_visible: bool = False,
    show: bool = True,
    save: bool = False,
    **kwargs,
) -> k3d.Plot | go.FigureWidget | None:
    """
    Visualize a 3D mesh using `pygel3d` or `k3d`. The visualization with the pygel3d backend provides higher-quality rendering, but it may take more time compared to using the k3d backend.

    Args:
        mesh (pygel3d.hmesh.Manifold): The input mesh object.
        backend (str, optional): The visualization backend to use.
            Choose between `pygel3d` (default) and `k3d`.
        wireframe (bool, optional): If True, displays the mesh as a wireframe.
            Works both with backend `pygel3d` and `k3d`. Defaults to True.
        flat_shading (bool, optional): If True, applies flat shading to the mesh.
            Works only with backend `k3d`. Defaults to True.
        grid_visible (bool, optional): If True, shows a grid in the visualization.
            Works only with backend `k3d`. Defaults to False.
        show (bool, optional): If True, displays the visualization inline, useful for multiple plots.
            Works only with backend `k3d`. Defaults to True.
        save (bool or str, optional): If True, saves the visualization as an HTML file.
            If a string is provided, it's interpreted as the file path where the HTML
            file will be saved. Works only with the backend `k3d`. Defaults to False.
        **kwargs (Any): Additional keyword arguments specific to the chosen backend:

            - `k3d.plot` kwargs: Arguments that customize the [`k3d.plot`](https://k3d-jupyter.org/reference/factory.plot.html) 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:
        k3d.Plot or None:

            - If `backend="k3d"`, returns a `k3d.Plot` object.
            - If `backend="pygel3d"`, the function displays the mesh but does not return a plot object.

    Raises:
        ValueError: If `backend` is not `pygel3d` or `k3d`.

    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/viz-k3d_mesh.png)


    """

    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)

    if backend not in ['k3d', 'pygel3d']:
        msg = "Invalid backend. Choose 'pygel3d' or 'k3d'."
        raise ValueError(msg)

    # Extract vertex positions and face indices
    face_indices = list(mesh.faces())
    vertices_array = np.array(mesh.positions())

    # Extract face vertex indices
    face_vertices = [
        list(mesh.circulate_face(int(fid), mode='v'))[:3] for fid in face_indices
    ]
    face_vertices = np.array(face_vertices, dtype=np.uint32)

    # Validate the mesh structure
    if vertices_array.shape[1] != 3 or face_vertices.shape[1] != 3:
        msg = 'Vertices must have shape (N, 3) and faces (M, 3)'
        raise ValueError(msg)

    # Separate valid kwargs for each backend
    valid_k3d_kwargs = {k: v for k, v in kwargs.items() if k not in ['smooth', 'data']}
    valid_pygel_kwargs = {k: v for k, v in kwargs.items() if k in ['smooth', 'data']}

    if backend == 'k3d':
        vertices_array = np.ascontiguousarray(vertices_array.astype(np.float32))
        face_vertices = np.ascontiguousarray(face_vertices)

        mesh_plot = k3d.mesh(
            vertices=vertices_array,
            indices=face_vertices,
            wireframe=wireframe,
            flat_shading=flat_shading,
        )

        # Create plot
        plot = k3d.plot(grid_visible=grid_visible, **valid_k3d_kwargs)
        plot += mesh_plot

        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

    elif backend == 'pygel3d':
        jd.set_export_mode(True)
        return jd.display(mesh, wireframe=wireframe, **valid_pygel_kwargs)

qim3d.viz.iso_surface

iso_surface(vol, colormap='Magma')

Creates an interactive iso-surface visualizer for a single surface level.

Parameters:

Name Type Description Default
vol ndarray

Volume to visualize an iso-surface of.

required
colormap str

(str, optional): Initial colormap for the iso-surface. This can be changed in the interface

'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('vol')
def iso_surface(vol: np.ndarray, colormap: str = 'Magma') -> None:
    """
    Creates an interactive iso-surface visualizer for a single surface level.

    Args:
        vol (np.ndarray): Volume to visualize an iso-surface of.
        colormap: (str, optional): Initial colormap for the iso-surface. This can be changed in the interface

    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(vol, colormap)

qim3d.viz.export_rotation

export_rotation(
    path,
    vol,
    degrees=360,
    num_frames=180,
    fps=30,
    image_size=(256, 256),
    color_map='magma',
    camera_height=2.0,
    camera_distance='auto',
    camera_focus='center',
    show=False,
)

Export a rotation animation of volume.

Parameters:

Name Type Description Default
path str

The path to save the output. The path should end with .gif, .avi, .mp4 or .webm. If no file extension is specified, .gif is automatically added.

required
vol ndarray

Volume to create .gif of.

required
degrees int

The amount of degrees for the volume to rotate. Defaults to 360.

360
num_frames int

The amount of frames to generate. Defaults to 180.

180
fps int

The amount of frames per second in the resulting animation. This determines the speed of the rotation of the volume. Defaults to 30.

30
image_size tuple of ints or None

Pixel size (width, height) of each frame. If None, the plotter's default size is used. Defaults to (256, 256).

(256, 256)
color_map str

Determines color map of volume. Defaults to 'magma'.

'magma'
camera_height float

Determines the height of the camera rotating around the volume. The float value represents a multiple of the height of the z-axis. Defaults to 2.0.

2.0
camera_distance int or string

Determines the distance of the camera from the center point. If 'auto' is used, it will be auto calculated. Otherwise a float value representing voxel distance is expected. Defaults to 'auto'.

'auto'
camera_focus list or str

Determines the voxel that the camera rotates around. Using 'center' will default to the center of the volume. Otherwise a list of three integers is expected. Defaults to 'center'.

'center'
show bool

If True, the resulting animation will be shown in the Jupyter notebook. Defaults to False.

False

Returns:

Type Description
None

None

Raises:

Type Description
TypeError

If the camera focus argument is incorrectly used.

TypeError

If the camera_distance argument is incorrectly used.

ValueError

If the path contains an unrecognized 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,
                          num_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,
    vol: np.ndarray,
    degrees: int = 360,
    num_frames: int = 180,
    fps: int = 30,
    image_size: tuple[int, int] | None = (256, 256),
    color_map: str = 'magma',
    camera_height: float = 2.0,
    camera_distance: float | str = 'auto',
    camera_focus: list | str = 'center',
    show: bool = False,
) -> None:
    """
    Export a rotation animation of volume.

    Args:
        path (str): The path to save the output. The path should end with .gif, .avi, .mp4 or .webm. If no file extension is specified, .gif is automatically added.
        vol (np.ndarray): Volume to create .gif of.
        degrees (int, optional): The amount of degrees for the volume to rotate. Defaults to 360.
        num_frames (int, optional): The amount of frames to generate. Defaults to 180.
        fps (int, optional): The amount of frames per second in the resulting animation. This determines the speed of the rotation of the volume. Defaults to 30.
        image_size (tuple of ints or None, optional): Pixel size (width, height) of each frame. If None, the plotter's default size is used. Defaults to (256, 256).
        color_map (str, optional): Determines color map of volume. Defaults to 'magma'.
        camera_height (float, optional): Determines the height of the camera rotating around the volume. The float value represents a multiple of the height of the z-axis. Defaults to 2.0.
        camera_distance (int or string, optional): Determines the distance of the camera from the center point. If 'auto' is used, it will be auto calculated. Otherwise a float value representing voxel distance is expected. Defaults to 'auto'.
        camera_focus (list or str, optional): Determines the voxel that the camera rotates around. Using 'center' will default to the center of the volume. Otherwise a list of three integers is expected. Defaults to 'center'.
        show (bool, optional): If True, the resulting animation will be shown in the Jupyter notebook. Defaults to False.

    Returns:
        None


    Raises:
        TypeError: If the camera focus argument is incorrectly used.
        TypeError: If the camera_distance argument is incorrectly used.
        ValueError: If the path contains an unrecognized 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,
                                  num_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(vol, (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=color_map)
    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 / num_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(num_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'))