Source code for timagetk.visu.stack

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#  Copyright (c) 2022 Univ. Lyon, ENS de Lyon, UCB Lyon 1, CNRS, INRAe, Inria
#  All rights reserved.
#  This file is part of the TimageTK library, and is released under the "GPLv3"
#  license. Please see the LICENSE.md file that should have been included as
#  part of this package.
# ------------------------------------------------------------------------------

"""Regroup plotting function for browsing stack images."""

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider
from pkg_resources import parse_version
from skimage import img_as_ubyte

from timagetk.bin.logger import get_logger
from timagetk.components.multi_channel import BlendImage
from timagetk.components.multi_channel import DEF_CHANNEL_COLORS
from timagetk.components.multi_channel import MultiChannelImage
from timagetk.components.multi_channel import chnames_generator
from timagetk.components.multi_channel import combine_channels
from timagetk.components.spatial_image import SpatialImage
from timagetk.visu.mplt import image_plot
from timagetk.visu.util import convert_str_range

log = get_logger(__name__)


[docs] def slider(label, mini, maxi, init, step=1, fmt="%1.0f"): """Matplotlib slider creation. Parameters ---------- label : str Name of the slider mini : int Min value of the slider maxi : int Max value of the slider init : int Initial value of the slider step : int, optional Step value of the slider fmt : str, optional Formatting of the displayed value selected by the slider Notes ----- The parameter `step` is not accessible for matplotlib version before 2.2.2. Returns ------- matplotlib.widgets.Slider A matplotlib slider to use in figures to select values """ from matplotlib import __version__ axcolor = 'lightgoldenrodyellow' rect = [0.25, 0.1, 0.65, 0.03] # [left, bottom, width, height] if parse_version(__version__) >= parse_version("2.2"): axz = plt.axes(rect, facecolor=axcolor) zs = Slider(axz, label=label, valmin=mini, valmax=maxi, valstep=step, closedmax=True, valinit=init, valfmt=fmt) else: axz = plt.axes(rect, axisbg=axcolor) zs = Slider(axz, label=label, valmin=mini, valmax=maxi, valstep=step, closedmax=True, valinit=init, valfmt=fmt) return zs
[docs] def stack_browser(image, title="", cmap='gray', val_range=None, axis='z', **kwargs): """Image stack browser, move along given axis, slice by slice. Use matplotlib widget (GUI agnostic). Parameters ---------- image : timagetk.components.spatial_image.SpatialImage 3D image to browse title : str, optional Title to give to the figure, *e.g.* the file name, default is empty cmap : matplotlib.colors.ListedColormap or str, optional Colormap to use, see the notes for advised colormaps val_range : list(int, int), {'type', 'auto'}, optional Minimum and maximum values to use for the colormap, can be given as a list of length 2 values. If None (default), set to 'auto' for a ``LabelledImage``, set to 'type' for a ``SpatialImage``. See the "Notes" section for detailled explanations. axis : str in ['x', 'y', 'z'], optional Axis to use for slicing Other Parameters ---------------- init_slice : int Slice id to use as starting point, default to ``0``. Should be inferior to max slice number for given axis extent : list of float If provided (default, ``None``), set the extent of the displayed image. By default, use the real unit extent of the SpatialImage istance. colorbar : bool If ``True`` (default ``False``), add a colorbar to the figure. Notes ----- We advise to use the **sequential** colormaps, such as: - *Sequential (2)* [mplt_cmap_sequential]_: `'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'summer', 'Wistia'`. - *Perceptually Uniform Sequential* [mplt_cmap_sequential2]_: `'viridis', 'plasma', 'inferno', 'magma', 'cividis'`. To understand the differences in "perception" induced by the different colormaps, see: [mplt_cmap_perception]_ Accepted str for `val_range` can be: - 'auto': get the min and max value of the image; - 'type': get the maximum range from the `image.dtype`, *e.g.* 'uint8'=[0, 255]; Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.visu.stack import stack_browser >>> fname = shared_dataset("p58", "intensity")[0] >>> img = imread(fname) >>> stack_browser(img, fname) >>> stack_browser(img, fname, axis='x') >>> from timagetk import LabelledImage >>> from timagetk.visu.util import greedy_colormap >>> fname = shared_dataset("p58", "segmented")[0] >>> img = imread(fname, LabelledImage, not_a_label=0) >>> stack_browser(img, fname, cmap=greedy_colormap(img), init_slice=80) >>> from timagetk.visu.stack import stack_browser >>> from timagetk.visu.util import greedy_colormap >>> from timagetk.array_util import dummy_labelled_image_3D >>> img = dummy_labelled_image_3D((0.2, 0.5, 0.5)) # ZYX sorted voxel-sizes >>> cmap = greedy_colormap(img) >>> stack_browser(img, "Dummy", cmap=cmap , init_slice=2) References ---------- .. [1] https://matplotlib.org/tutorials/colors/colormaps.html#sequential .. [2] https://matplotlib.org/tutorials/colors/colormaps.html#sequential2 .. [3] https://matplotlib.org/tutorials/colors/colormaps.html#mycarta-banding """ from matplotlib.colors import BoundaryNorm fig, ax = plt.subplots() plt.subplots_adjust(bottom=0.25) # save some space for the slider norm, bounds = None, None if isinstance(cmap, str): if val_range is None: val_range = 'type' if isinstance(val_range, str): val_range = convert_str_range(image, val_range) mini, maxi = val_range if cmap == 'glasbey': from timagetk.visu.util import get_glasbey bkgd = getattr(image, 'background', 1) nal = getattr(image, 'not_a_label', 0) cmap = get_glasbey(np.unique(image), not_a_label=nal, background=bkgd) else: from matplotlib import colormaps as cm cmap = cm.get_cmap(cmap) else: # Assume its a valid matplotlib ListedColormap mini, maxi = int(image.min()), int(image.max()) values_spread = maxi - mini + 1 try: assert values_spread == cmap.N except AssertionError: raise ValueError(f"Number of colors {cmap.N} and values {values_spread} does not match!") else: val_range = [None, None] bounds = np.linspace(mini - 0.5, maxi + 0.5, cmap.N + 1) norm = BoundaryNorm(bounds, cmap.N) # - Get the slice to represent: init_slice = kwargs.get('init_slice', 0) ax, l = image_plot(image.get_slice(init_slice, axis), ax, cmap=cmap, val_range=val_range, norm=norm) plt.title(title) if kwargs.get('colorbar', False): if bounds is not None: if maxi < 100: ticks_step = 1 while len(bounds) / ticks_step > 20: ticks_step += 1 else: ticks_step = 10 while len(bounds) / ticks_step > 20: ticks_step += 10 fig.colorbar(l, ax=ax, ticks=bounds[::ticks_step][:-1] + 0.5, boundaries=bounds) else: fig.colorbar(l, ax=ax) max_slice = image.get_shape(axis) - 1 zs = slider(label='{}-slice'.format(axis), mini=0, maxi=max_slice, init=init_slice, step=1) def update(val): slice_id = int(zs.val) l.set_data(image.get_slice(slice_id, axis).get_array()) fig.canvas.draw_idle() zs.on_changed(update) plt.show() return zs
[docs] def stack_browser_threshold(image, title="", cmap='gray', val_range=None, axis='z', **kwargs): """Stack browser, by slices along given axis. Use matplotlib widget (GUI agnostic). Parameters ---------- image : timagetk.SpatialImage 3D image to browse title : str, optional Title to give to the figure, *e.g.* the file name, default is empty cmap : str, optional Colormap to use, see the notes for advised colormaps val_range : list(int, int), {'type', 'auto'}, optional Minimum and maximum values to use for the colormap, can be given as a list of length 2 values. If None (default), set to 'auto' for a ``LabelledImage``, set to 'type' for a ``SpatialImage``. See notes for more explanations. axis : str in ['x', 'y', 'z'], optional Axis to use for slicing Other Parameters ---------------- init_slice : int Slice id to use as starting point, should be inferior to max slice number for given axis extent : list of float If provided (default, ``None``), set the extent of the displayed image. By default, use the real unit extent of the SpatialImage istance. Notes ----- We advise to use the **sequential** colormaps, such as: - *Sequential (2)* [mplt_cmap_sequential]_: `'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'summer', 'Wistia'`. - *Perceptually Uniform Sequential* [mplt_cmap_sequential2]_: `'viridis', 'plasma', 'inferno', 'magma', 'cividis'`. To understand the differences in "perception" induced by the different colormaps, see: [mplt_cmap_perception]_ Accepted str for `val_range` can be: - 'auto': get the min and max value of the image; - 'type': get the maximum range from the `image.dtype`, *e.g.* 'uint8'=[0, 255]; Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.stack import stack_browser_threshold >>> fname = 'p58-t0-a0.lsm' >>> img = imread(shared_data(fname, "p58")) >>> b = stack_browser_threshold(img, fname) # do not forget to assign to a variable because "you need to keep the sliders around globally" References ---------- .. [mplt_cmap_sequential] https://matplotlib.org/tutorials/colors/colormaps.html#sequential .. [mplt_cmap_sequential2] https://matplotlib.org/tutorials/colors/colormaps.html#sequential2 .. [mplt_cmap_perception] https://matplotlib.org/tutorials/colors/colormaps.html#mycarta-banding """ fig, ax = plt.subplots() plt.subplots_adjust(bottom=0.25) # save some space for the slider if val_range is None: val_range = 'type' if isinstance(val_range, str): val_range = convert_str_range(image, val_range) from timagetk.components.labelled_image import LabelledImage from timagetk.visu.util import get_glasbey if cmap == 'glasbey' and isinstance(image, LabelledImage): cmap = get_glasbey(image.labels(), not_a_label=image.not_a_label, background=getattr(image, 'background', 1)) # - Get the slice to represent: ax, l = image_plot(image.get_slice(0, axis), ax, cmap=cmap, val_range=val_range) plt.title(title) if isinstance(cmap, str): plt.colorbar(l, ax=ax) ax_slice = plt.axes([0.25, 0.1, 0.65, 0.03]) ax_threshold = plt.axes([0.25, 0.15, 0.65, 0.03]) max_slice = image.get_shape(axis) - 1 zs = Slider(ax=ax_slice, label='{}-slice'.format(axis), valmin=0, valmax=max_slice, valinit=kwargs.get('init_slice', 0), valstep=1) init_threshold = kwargs.get('init_threshold', (val_range[1] - val_range[0]) // 2) th_step = kwargs.get('th_step', 0.05 if image.dtype == 'float' else 1) mask = SpatialImage(image.get_slice(0, axis) > init_threshold, voxelsize=image.voxelsize) from matplotlib.colors import ListedColormap cm = ListedColormap([[1., 0., 0., 1.], [0., 0., 0., 0.]]) ax, l_th = image_plot(mask, ax, cmap=cm, val_range=[0, 1]) ths = Slider(ax=ax_threshold, label='Threshold', valmin=val_range[0], valmax=val_range[1], valinit=init_threshold, valstep=th_step) def update(val): # Get slice id and update: slice_id = int(zs.val) l.set_data(image.get_slice(slice_id, axis).get_array()) # Get threshold value and update if isinstance(th_step, int): threshold = int(ths.val) else: threshold = float(ths.val) l_th.set_data(image.get_slice(slice_id, axis).get_array() > threshold) fig.canvas.draw_idle() zs.on_changed(update) ths.on_changed(update) plt.show() return zs, ths
[docs] def rgb_stack_browser(image, title="", cmap='gray', val_range=None, axis='z', **kwargs): """RGB stack browser, along last physical dimension. Use matplotlib widget (GUI agnostic). Parameters ---------- image : timagetk.BlendImage or timagetk.MultiChannelImage An RGB 3D array to browse along its last "physical" dimension ``P``, *i.e.* not the RGB values. title : str, optional Title to give to the figure, *e.g.* the file name, default is empty cmap : str, optional Colormap to use, see the notes for advised colormaps val_range : list(int, int), {'type', 'auto'}, optional Minimum and maximum values to use for the colormap, can be given as a list of length 2 values. If None (default), set to 'auto' for a ``LabelledImage``, set to 'type' for a ``SpatialImage``. See notes for more explanations. axis : str in ['x', 'y', 'z'], optional Axis to use for slicing Other Parameters ---------------- init_slice : int Slice id to use as starting point, should be inferior to max slice number for given axis extent : list of float If provided (default, ``None``), set the extent of the displayed image. By default, use the real unit extent of the SpatialImage istance. Notes ----- As Numpy and Matplotlib have different axis order convention, we transpose the first two axis of the given numpy array to display the first axis (rows, 'X') horizontally per the usual plotting convention. If specified, the ``xy_ratio`` is also inverted to match that modification. Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.stack import rgb_stack_browser >>> from skimage.color import gray2rgb >>> fname = 'p58-t0-a0.lsm' >>> img = imread(shared_data(fname, "p58")) >>> rgb_img = gray2rgb(img) >>> # imread return XYZ SpatialImage, the browser will move along the z-plane: >>> rgb_stack_browser(rgb_img,"z-plane browsing") >>> # transposing Y & Z axis, the browser will now move along the y-plane: >>> rgb_stack_browser(rgb_img.transpose((0,2,1,3)),"y-plane browsing") >>> # Better example with an actual RGB image >>> from timagetk.components.multi_channel import label_blending >>> from timagetk.tasks.segmentation import watershed_segmentation >>> from timagetk.components.multi_channel import label_blending >>> img = imread(shared_data("p58-t0-a0.lsm", "p58")) >>> vx, vy, vz = img.voxelsize >>> seg_img = watershed_segmentation(img, hmin=10) >>> blend = label_blending(seg_img, img) >>> b = rgb_stack_browser(blend,"Intensity & segmentation blending",xy_ratio=vx/vy) """ assert image.ndim == 4 fig, ax = plt.subplots() plt.subplots_adjust(bottom=0.25) # save some space for the slider # - Get the first slice to represent: ax, l = image_plot(image.get_slice(0, axis), ax, cmap=cmap, val_range=val_range) plt.title(title) max_slice = image.get_shape(axis) - 1 zs = slider(label='{}-slice'.format(axis), mini=0, maxi=max_slice, init=kwargs.get('init_slice', 0), step=1) def update(val): slice_id = int(zs.val) l.set_data(image.get_slice(slice_id, axis).get_array()) fig.canvas.draw_idle() zs.on_changed(update) plt.show() return zs
[docs] def channels_stack_browser(images, channel_names=None, colors=DEF_CHANNEL_COLORS, title="", axis='z', **kwargs): """Multi channel stack browser, by slices along given axis. Use matplotlib widget (GUI agnostic). Parameters ---------- images : list of SpatialImage, MultiChannelImage List of 3D SpatialImage to browse channel_names : list of str, optional List of channel names colors : list of str in ``DEF_CHANNEL_COLORS``, optional List of color to use, see ``combine_channels`` for known names title : str, optional Title to give to the figure, *e.g.* the file name, default is empty axis : str, in ['x', 'y', 'z'] Axis to use for slicing Other Parameters ---------------- init_slice : int Slice id to use as starting point, should be inferior to max slice number for given axis extent : list of float If provided (default, ``None``), set the extent of the displayed image. By default, use the real unit extent of the SpatialImage istance. See Also -------- visu.util.DEF_CHANNEL_COLORS: the list of available channel colors. Examples -------- >>> from timagetk.io.util import shared_data >>> from timagetk.io import imread >>> from timagetk.algorithms.blockmatching import blockmatching >>> from timagetk.visu.stack import channels_stack_browser >>> from timagetk.algorithms.trsf import apply_trsf >>> from timagetk.algorithms.quaternion import centered_rotation_trsf >>> # EXAMPLE: Visualize the registration of a floating image (green) on a reference image (red) >>> float_img = imread(shared_data('p58-t0-a0.lsm', "p58")) >>> ref_img = imread(shared_data('p58-t0-a1.lsm', "p58")) >>> trsf_z = centered_rotation_trsf(ref_img, -90, 'z') # Initialize registration with a -90° rotation along Z >>> trsf = blockmatching(float_img, ref_img, method='affine', init_trsf=trsf_z) # Intensity registration >>> res_img = apply_trsf(float_img, trsf, template_img=ref_img) # Apply trnsformation to floating image >>> b = channels_stack_browser([res_img, ref_img], ['t0 on t1', 't1'], ['green', 'red'], "affine registration") """ from matplotlib.widgets import CheckButtons from skimage.color import gray2rgb if isinstance(images, MultiChannelImage): channel_names = images.channel_names images = [images[ch] for ch in channel_names] else: if channel_names is None: channel_names = chnames_generator(len(images)) assert len(images) == len(channel_names) assert len(channel_names) <= len(colors) # -- Variables definition: n_channels = len(images) visible = [True] * n_channels used_colors = colors max_slice = images[0].get_shape(axis) - 1 def selected_channels(images, visible): # exchange x & y axis for representation: numpy and matplotlib do not have the same the same coordinates conventions!!! return [img for n, img in enumerate(images) if visible[n]] # -- Create matplotlib figure: fig, ax = plt.subplots() plt.subplots_adjust(left=0.2, bottom=0.25) # -- Initialise blending for z-slice=0 if isinstance(images, list): blend = combine_channels(selected_channels(images, visible), colors) else: blend = images ax, l = image_plot(blend.get_slice(0, axis), ax) plt.title(title) # -- Slider: z-slice selection zs = slider(label='{}-slice'.format(axis), mini=0, maxi=max_slice, init=kwargs.get('init_slice', 0), step=1) def update(val): """Update the blend image associated to new `zs.val`.""" slice_id = int(zs.val) if sum(visible) == 0: blend = np.zeros_like(gray2rgb(images[0].get_slice(0, axis), alpha=False), dtype=images[0].dtype) else: blend = combine_channels(selected_channels(images, visible), used_colors).get_slice(slice_id, axis) l.set_data(blend) fig.canvas.draw_idle() zs.on_changed(update) # -- CheckButtons: channels selection: rax = plt.axes([0.05, 0.4, 0.1, 0.15]) check = CheckButtons(rax, channel_names, visible) def channel_select(ch_name): """Update the blend image associated to new channels `visible`.""" slice_id = int(zs.val) index = channel_names.index(ch_name) if visible[index]: visible[index] = False else: visible[index] = True used_colors = [colors[n] for n in range(n_channels) if visible[n]] if sum(visible) == 0: blend = np.zeros_like(gray2rgb(images[0].get_slice(0, axis), alpha=False), dtype=np.uint8) else: blend = combine_channels(selected_channels(images, visible), used_colors).get_slice(slice_id, axis) l.set_data(blend) fig.canvas.draw_idle() check.on_clicked(channel_select) plt.show() return zs, check
[docs] def stack_panel(image, axis='z', step=1, start=0, stop=-1, **kwargs): """Create a panel of slices taken from `image` along given `axis`. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage or timagetk.components.spatial_image.LabelledImage or timagetk.components.multi_channel.BlendImage or timagetk.components.multi_channel.MultiChannelImage Image to represent as a panel of slices axis : str, optional Axis along which to move to get the slices to display, default is 'z' step : int, optional Slices step, default is 1 start : int, optional Starting slice, default is first stop : int, optional Stopping slice, default is last Other Parameters ---------------- suptitle : str A general title placed above the sub-figures titles, usually used when a list of images is given max_per_line : int Number of figure per line when using more than one images. Defaults to ``4``. thumb_size : float Image size in inch (default=5.) val_range : {'auto', 'type'} or list of int, optional Define the range of values used by the colormap. Defaults to ``'type'``. See the "Notes" section for detailled explanations. cmap : str Colormap to use, see the ``stack_browser`` notes for advised colormaps figname : str, optional If provided (default is empty), the image will be saved under this filename. Raises ------ ValueError If given `start` value is above `axis` max dimension - `step` Notes ----- Accepted ``val_range`` may be: - **auto**, get the min and max value of the image; - **type** (default), get the maximum range from the `image.dtype`, *e.g.* 'uint8'=[0, 255]; - length-2 list of value, *e.g.* [10, 200]; Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.visu.stack import stack_panel >>> img = imread(shared_dataset("p58", "intensity")[0]) >>> stack_panel(img, axis="z", step=5) >>> stack_panel(img, axis="z", step=5, suptitle=img.filename) >>> stack_panel(img, axis="x", step=10, suptitle=img.filename) >>> from timagetk import LabelledImage >>> from timagetk.components.multi_channel import label_blending >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.visu.stack import rgb_stack_browser >>> from timagetk.visu.stack import stack_panel >>> int_path = shared_dataset("sphere", 'intensity')[0] >>> seg_path = shared_dataset("sphere", 'segmented')[0] >>> int_img = imread(int_path) >>> seg_img = LabelledImage(imread(seg_path), not_a_label=0) >>> blend = label_blending(seg_img, int_img) >>> stack_panel(blend, step=10, suptitle="Intensity & segmentation blending") >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.algorithms.blockmatching import blockmatching >>> from timagetk.algorithms.trsf import apply_trsf >>> from timagetk.visu.stack import stack_panel >>> int_imgs = shared_dataset("p58", "intensity") >>> flo_img = imread(int_imgs[0]) >>> ref_img = imread(int_imgs[1]) >>> rigid_trsf = blockmatching(flo_img, ref_img, method='rigid') >>> reg_img = apply_trsf(flo_img, rigid_trsf, template_img=ref_img, interpolation='linear') >>> from timagetk import MultiChannelImage >>> mc = MultiChannelImage([reg_img, ref_img], channel_names=['Registered', 'Reference']) >>> stack_panel(mc, axis="z", step=mc.get_shape("z")//5) """ assert image.is3D() max_sl = image.get_shape(axis) if stop == -1: stop = max_sl - 1 if stop > max_sl: msg = "Given `stop` () is above max dimension along {}-axis, set to {}" log.error(msg.format(stop, axis, max_sl)) if start > max_sl - step: msg = "Given `start` () is above {}-axis max dimension - `step`!" raise ValueError(msg.format(start, axis)) if kwargs.get('thumb_size', None) is None: kwargs.update({'thumb_size': 2.}) if kwargs.get('max_per_line', None) is None: kwargs.update({'max_per_line': 6}) slices = np.arange(start, stop, step) stack = [] for sl in slices: im_sl = image.get_slice(int(sl), axis=axis) stack.append(im_sl) fname = kwargs.pop('figname', "") if 'cmap' not in kwargs: kwargs['cmap'] = 'gray' kwargs['title'] = [f"{axis}-slice {sl}/{max_sl}" for sl in slices] kwargs['no_show'] = True # to disable call to `plt.show` in `_multiple_plots` from timagetk.visu.mplt import _multiple_plots from timagetk.visu.mplt import image_plot fig, axes = _multiple_plots(image_plot, stack, cbar=False, **kwargs) try: assert image.filename != "" except: fig.suptitle("Stack panel") else: fig.suptitle(f"{image.filename} - Stack panel") if fname != "": plt.savefig(fname) else: plt.show() return
[docs] def orthogonal_view(image, x_slice=None, y_slice=None, z_slice=None, suptitle="", figname="", cmap='gray', **kwargs): """Orthogonal representation of an image by three slices along each axes. Slice numbering starts at 0 (like indexing). Parameters ---------- image : numpy.ndarray or timagetk.components.spatial_image.SpatialImage or timagetk.components.multi_channel.MultiChannelImage or timagetk.components.multi_channel.BlendImage Image from which to extract the slice. x_slice : int, optional Value defining the slice to represent in x direction. By default, the middle of the axis. y_slice : int, optional Value defining the slice to represent in y direction. By default, the middle of the axis. z_slice : int, optional Value defining the slice to represent in z direction. By default, the middle of the axis. suptitle : str, optional If provided (default is empty), add this string of characters as title. figname : str or pathlib.Path, optional If provided (default is empty), the image will be saved under this filename. cmap : str Colormap to use, see the notes of ``stack_browser`` for advised colormaps. Other Parameters ---------------- val_range : {'type', 'auto'} or list of int Define the range of values used by the colormap. Defaults to ``'type'``. See the "Notes" section for detailled explanations. figsize : tuple Lenght two tuple defining desired figure size. Defaults to ``(10, 10)``. dpi : int The resolution in dots per inch. Defaults to ``96``. Notes ----- Accepted ``val_range`` may be: - **auto**, get the min and max value of the image; - **type** (default), get the maximum range from the `image.dtype`, *e.g.* 'uint8'=[0, 255]; - length-2 list of value, *e.g.* [10, 200]; Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.visu.stack import orthogonal_view >>> # EXAMPLE 1 - Orthogonal view of a grayscale intensity image: >>> image = imread(shared_dataset("p58", "intensity")[0]) >>> orthogonal_view(image, suptitle=image.filename) >>> # EXAMPLE 2 - Orthogonal view of a labelled image: >>> image = imread(shared_dataset("p58", "segmented")[0]) >>> orthogonal_view(image, cmap='glasbey', val_range='auto', suptitle=image.filename) """ # Transform the ``MultiChannelImage`` into a ``BlendImage`` (RGB array): if isinstance(image, MultiChannelImage): image = BlendImage([image[ch] for ch in image.get_channel_names()]) x_sh, y_sh, z_sh = image.get_shape()[::-1] x_ext, y_ext, z_ext = image.get_extent()[::-1] x_vxs, y_vxs, z_vxs = image.get_voxelsize()[::-1] if x_slice is None: x_slice = x_sh // 2 if y_slice is None: y_slice = y_sh // 2 if z_slice is None: z_slice = z_sh // 2 if isinstance(image, (SpatialImage, BlendImage)): x_sl = image.get_slice(x_slice, 'x').transpose('yz') y_sl = image.get_slice(y_slice, 'y') z_sl = image.get_slice(z_slice, 'z') else: raise TypeError(f"Unknown image type to `orthogonal_view`: {type(image)}") # If RGB image is unsigned 16bits, convert it to 8bits for representation: if isinstance(image, BlendImage) and image.dtype.name == 'uint16': log.debug("Converting 'uint16' image to 'uint8' for display.") x_sl = BlendImage(img_as_ubyte(x_sl), voxelsize=x_sl.voxelsize, axes_order=x_sl.axes_order) y_sl = BlendImage(img_as_ubyte(y_sl), voxelsize=y_sl.voxelsize, axes_order=y_sl.axes_order) z_sl = BlendImage(img_as_ubyte(z_sl), voxelsize=z_sl.voxelsize, axes_order=z_sl.axes_order) val_range = kwargs.pop('val_range', 'type') if kwargs.get('figsize', None) is None: kwargs['figsize'] = (10, 10) if kwargs.get('dpi', None) is None: kwargs['dpi'] = 96 fig = plt.figure(**kwargs) gs = fig.add_gridspec(2, 2, width_ratios=[x_ext, z_ext], height_ratios=[y_ext, z_ext], hspace=0.2, wspace=0.01) # -- Plot the z-slice / xy-plane image (GREEN): xy_im = fig.add_subplot(gs[0, 0]) xy_im.plot([x_slice * x_vxs, x_slice * x_vxs], [0, y_sh * y_vxs], color='red') # the x-slice position xy_im.plot([0, x_sh * x_vxs], [y_slice * y_vxs, y_slice * y_vxs], color='blue') # the y-slice position xy_im, xy_fig = image_plot(z_sl, xy_im, cmap=cmap, val_range=val_range) xy_im.set_title('z-slice {}/{}'.format(z_slice, z_sh)) # -- Plot the y-slice / xz-plane image (BLUE): xz_im = plt.subplot(gs[1, 0]) xz_im.plot([x_slice * x_vxs, x_slice * x_vxs], [0, y_sh * y_vxs], color='red') xz_im.plot([0, x_sh * x_vxs], [z_slice * z_vxs, z_slice * z_vxs], color='green') xz_im, xz_fig = image_plot(y_sl, xz_im, cmap=cmap, val_range=val_range) xz_im.set_title('y-slice {}/{}'.format(y_slice, y_sh)) xz_im.axes.xaxis.set_ticklabels([]) # -- Plot the x-slice / yz-plane image (RED): yz_im = plt.subplot(gs[0, 1]) yz_im.plot([0, z_sh * z_vxs], [y_slice * y_vxs, y_slice * y_vxs], color='blue') yz_im.plot([z_slice * z_vxs, z_slice * z_vxs], [0, y_sh * y_vxs], color='green') yz_im, yz_fig = image_plot(x_sl, yz_im, cmap=cmap, val_range=val_range) yz_im.set_title('x-slice {}/{}'.format(x_slice, x_sh)) yz_im.axes.yaxis.set_ticklabels([]) # Change the line width and colors of the image border to match slices positions: colors = ["green", "blue", "red"] for n, ax in enumerate([xy_im, xz_im, yz_im]): for axis in ['top', 'bottom', 'left', 'right']: ax.spines[axis].set_linewidth(2) ax.spines[axis].set_color(colors[n]) # Add suptitle: plt.suptitle(suptitle) # plt.subplots_adjust(wspace=0.01) # plt.subplots_adjust(hspace=0.09) if figname != "": plt.savefig(figname) else: plt.show() return