Source code for timagetk.visu.mplt

#!/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.
# ------------------------------------------------------------------------------

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import gridspec
from skimage.exposure import cumulative_distribution
from skimage.exposure import histogram
from tqdm import tqdm

from timagetk import MultiChannelImage
from timagetk.algorithms.connexe import connected_components
from timagetk.algorithms.regionalext import regional_extrema
from timagetk.algorithms.watershed import watershed
from timagetk.bin.logger import get_logger
from timagetk.components.multi_channel import BlendImage
from timagetk.components.multi_channel import combine_channels
from timagetk.components.multi_channel import label_blending
from timagetk.components.spatial_image import SpatialImage
from timagetk.util import type_to_range
from timagetk.visu.util import _force_aspect_ratio
from timagetk.visu.util import convert_str_range

log = get_logger(__name__)


[docs] def image_plot(image, axe=None, cmap="gray", **kwargs): """Display a 2D image with size unit and a coordinate centered pixel. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage 2D image to represent. axe : matplotlib.axes.Axes Use it to combine plots in the same subfigure. cmap : Any The Colormap instance or registered colormap name used to map scalar data to colors. This parameter is ignored for RGB(A) data. Other Parameters ---------------- val_range : str or list of int Define the range of values used by the colormap, by default `'type'`. norm : matplotlib.colors.BoundaryNorm The `.Normalize` instance used to scale scalar data to the [0, 1] range before mapping to colors using *cmap*. By default, a linear scaling mapping the lowest value to 0 and the highest to 1 is used. This parameter is ignored for RGB(A) data. extent : list of float If provided, set the extent of the displayed image. By default, try to use the real extent of the SpatialImage instance or set to ``image.shape`` (voxel unit). voxelsize : list of float If provided, set the voxelsize of the displayed image. By default, try to use the real voxelsize of the SpatialImage instance or set to ``[1., 1.]`` (voxel unit). unit : str If provided, set the unit on the image axes. By default, try to use the real unit of the SpatialImage instance or set to ``'voxels'`` (voxel unit). interpoplation : str The interpolation method used. See notes for more details. Defaults to 'none'. Returns ------- matplotlib.axes.Axes The updated `Axes` object. matplotlib.axes.Axes The sub-figure object. Notes ----- Supported values for `interpolation` are 'none', 'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'blackman'. See ``matplotlib.axes._axes.Axes.imshow`` documentation for more details. See Also -------- matplotlib.axes._axes.Axes.imshow Examples -------- >>> import matplotlib.pyplot as plt >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.mplt import image_plot >>> fname = 'p58-t0-a0.lsm' >>> image_path = shared_data(fname, "p58") >>> img = imread(image_path) >>> # - Display the middle z-slice of the 3D image, with 'viridis' colormap: >>> fig = plt.figure() >>> ax_img = plt.subplot() >>> mid_z = img.get_slice(img.get_shape('z')//2, 'z') # get the middle z-slice >>> ax_img, fig_img = image_plot(mid_z,ax_img,cmap='viridis') >>> axe.set_title(fname) # add the file name as title >>> fig.colorbar(fig_img, ax=axe, label='intensity') # add the intensity colorbar """ val_range = kwargs.pop('val_range', 'type') if isinstance(val_range, str): val_range = convert_str_range(image, val_range) else: assert isinstance(val_range, (list, tuple)) and len(val_range) == 2 mini, maxi = val_range norm = kwargs.pop('norm', None) if norm is not None: mini, maxi = None, None # Using `norm` override the `vmin` & `vmax` parameters in `imshow` try: y_lab, x_lab = image.axes_order except ValueError: y_lab, x_lab = image.axes_order[:2] if 'extent' in kwargs and len(kwargs['extent']) == 2: # Override by keyword argument: extent = kwargs.pop('extent') else: try: extent = image.get_extent() # This is real world units except: extent = kwargs.get('extent', image.shape) # By default, assume we are in voxel units if 'voxelsize' in kwargs and len(kwargs['voxelsize']) == 2: # Override by keyword argument: vxs = kwargs.pop('voxelsize') else: try: vxs = image.get_voxelsize() # This is real world units except: vxs = kwargs.get('voxelsize', [1., 1.]) # By default, assume we are in voxel units if 'unit' in kwargs and isinstance(kwargs['unit'], str): # Override by keyword argument: unit = kwargs.pop('unit') else: try: unit = image.get_unit() # This is real world units except: unit = kwargs.get('unit', 'voxels') # By default, assume we are in voxel units x_lab += f" ({unit})" # add the size unit to the axis label y_lab += f" ({unit})" # add the size unit to the axis label # The pixel coordinate should be at its center: x_offset = vxs[1] / 2 y_offset = vxs[0] / 2 mpl_extent = (-x_offset, extent[1] + x_offset, extent[0] + y_offset, -y_offset) if isinstance(image, SpatialImage): arr = image.get_array() # get an array instance elif isinstance(image, MultiChannelImage): arr = combine_channels([image.get_channel(ch) for ch in image.get_channel_names()]) else: arr = image interp = kwargs.get('interpolation', 'none') fig_img = axe.imshow(arr, cmap=cmap, vmin=mini, vmax=maxi, extent=mpl_extent, interpolation=interp, origin='upper', norm=norm) axe.xaxis.tick_top() # move the x-axis to the top if kwargs.get('title', False): axe.set_title(kwargs['title']) if kwargs.get('no_xtick', False): fig_img.axes.xaxis.set_ticklabels([]) if kwargs.get('no_ytick', False): fig_img.axes.yaxis.set_ticklabels([]) if kwargs.get('no_xlab', False): axe.set_xlabel("") else: axe.set_xlabel(x_lab) # add the axis label if kwargs.get('no_ylab', False): axe.set_ylabel("") else: axe.set_ylabel(y_lab) # add the axis label return axe, fig_img
[docs] def vignette(image, cmap="gray", **kwargs): """Create a vignette of a 2D image. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage 2D image to represent. cmap : Any The Colormap instance or registered colormap name used to map scalar data to colors. This parameter is ignored for RGB(A) data. Other Parameters ---------------- figname : str If a string is given, consider it's a valid file location (path, name & extension) Do not show the figure, just create, write & close. interpoplation : str The interpolation method used. See notes for more details. Defaults to 'none'. """ fig = plt.figure(figsize=np.array(image.get_extent()) / 10.) ax = plt.Axes(fig, [0., 0., 1., 1.]) ax.set_axis_off() fig.add_axes(ax) ax, fig_img = image_plot(image, ax, cmap=cmap, no_xtick=True, no_ytick=True, no_xlab=True, no_ylab=True, **kwargs) figname = kwargs.get('figname', "") # Save the figure if a 'figname' is given: if figname != "": plt.savefig(figname) return fig_img else: plt.show() return None
[docs] def imshow_property(image, ppty, slice_id=None, axis="z", val_range="auto", cmap='viridis', **kwargs): """Plot a labelled image with property values instead of labels. Parameters ---------- image : timagetk.LabelledImage Labelled image to map property values. ppty : dict Label based dictionary of values. slice_id : int, optional Use all slices by default (None), else the z-slice to get for the histogram axis : str, optional Axis to use when specifying a `slice_id`, ``'z'`` by default. val_range : 2-list of int, optional Define the range of values used by the colormap. By default, ``"auto"`` scale it to the range of mapped property. cmap : str, optional Name of the colormap to use, defaults to ``"viridis"``. Examples -------- >>> from timagetk.io import imread >>> from timagetk import TissueImage3D >>> from timagetk.io.util import shared_dataset >>> from timagetk.visu.mplt import imshow_property >>> from timagetk.visu.mplt import grayscale_imshow >>> from timagetk.components.labelled_image import labels_at_stack_margins >>> from timagetk.features.cells import volume >>> image_path = shared_dataset("p58", "segmented")[0] >>> image = imread(image_path, TissueImage3D, background=1, not_a_label=0) >>> # List cells too close to stack margin, as they should be excluded: >>> margin_labels = labels_at_stack_margins(image, 5) >>> # Compute the cell volumes: >>> vol = volume(image, cell_ids=set(image.cell_ids())-set(margin_labels)) >>> axis = 'y' >>> mid_sh = image.get_shape(axis) // 2 >>> imshow_property(image, vol, mid_sh, axis, cmap='viridis', ppty_name="Volume") >>> imshow_property(image, vol, cmap='viridis') """ thumb_size = kwargs.pop('thumb_size', 8.) title = kwargs.pop('title', '') ppty_name = kwargs.pop('ppty_name', '') if title == "": title = image.filename title_suffix = "" if image.is3D(): if slice_id is not None: max_slice = image.get_shape(axis) image = image.get_slice(slice_id, axis=axis) title_suffix = '{}-slice ({}/{})'.format(axis.upper(), slice_id, max_slice) else: title_suffix = "Z-projection" from timagetk.algorithms.reconstruction import project_segmentation image = project_segmentation(image, axis=axis, orientation=kwargs.pop('orientation', 1), background=image.background) # labels = np.unique(image) # get the list of labels in the 2D image # assert all([l in ppty for l in labels]) # make sure we have all of them mapped to a value if val_range == 'auto': val_range = [min(ppty.values()), max(ppty.values())] else: assert isinstance(val_range, (list, tuple)) and len(val_range) == 2 from timagetk.components.labelled_image import relabel_with_property img = relabel_with_property(image, ppty) fig, ax = plt.subplots() ye, xe = img.extent fig.set_size_inches(w=thumb_size, h=thumb_size * ye / xe) if ppty_name != "": fig.suptitle(ppty_name) ax, fig_img = image_plot(img, ax, cmap=cmap, val_range=val_range) fig.colorbar(fig_img, ax=ax) if title_suffix != '': title += " - " + title_suffix ax.set_title(title) figname = kwargs.get('figname', "") # Save the figure if a 'figname' is given: if figname != "": plt.savefig(figname) return None elif kwargs.get('no_show', False): # Using 'no_show' keyword argument to build _multiple_plots, w return fig_img else: plt.show() return None
[docs] def grayscale_imshow(image, slice_id=None, axis="z", val_range='type', cmap='gray', **kwargs): """Display a 2D grayscale image. Uses ``matplotlib.imshow()`` and ``_multiple_plots`` if more than one `image` is given. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage or list of timagetk.components.spatial_image.SpatialImage 2D image(s) to represent, if 3D image(s), a ``contour_projection`` is first computed. slice_id : int, optional Use all slices by default (None), else the z-slice to get for the histogram axis : str, optional Axis to use when specifying a `slice_id`, ``'z'`` by default. val_range : str or list of str, optional Define the range of values used by the colormap, by default *type* cmap : matplotlib.colors.ListedColormap or str or list of str, optional Colormap to use, see the ``stack_browser`` notes for advised colormaps Other Parameters ---------------- title : str If provided (default is empty), add this string of characters as title suptitle : str A general title placed above the sub-figures titles, usually used when a list of images is given threshold : int The intensity threshold to use with contour projection when using 3D images. Setting `slice_id` override the use of this parameter. orientation : {-1, 1} The orinetation of the image, use `-1` with an inverted microscope. In that case the top of the object is at the bottome of the image. axe : `.axes.Axes` object Use it to combine plots in the same subfigure cbar : bool If ``False``, default is ``True``, no colorbar will be displayed 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. max_per_line : int Number of figure per line when using more than one images, default is 4 thumb_size : float Image size in inch (default=5.) no_show : bool If ``True``, default is ``False``, do not call the blocking 'show' method figname : str If a string is given, consider it's a valid file location (path, name & extension) Do not show the figure, just create, write & close. interpoplation : str The interpolation method used. See notes for more details. Defaults to 'none'. Returns ------- AxesSubplot Use it to combine plots in the same subfigure. Notes ----- Supported values for `val_range` are: - **auto**, get the min and max value of the image; - **type**, get the maximum range from the `image.dtype`, *e.g.* 'uint8'=[0, 255]; - length-2 list of value, *e.g.* [10, 200]; If a list of `image` is given, a list of `title` should also be given. If a list of `image` is given and only one `val_range` is given, we use the same range for all images. Supported values for `interpolation` are 'none', 'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'blackman'. See ``matplotlib.axes._axes.Axes.imshow`` documentation for more details. See Also -------- matplotlib.axes._axes.Axes.imshow Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.visu.mplt import grayscale_imshow >>> # - Get 'p58' shared intensity image: >>> image = imread(shared_dataset("p58")[0]) >>> # Example 1 - Display the middle z-slice of the 3D image stack, with 'viridis' colormap: >>> mid_z = image.get_shape('z')//2 >>> v = grayscale_imshow(image, mid_z, 'z', cmap='viridis', colorbar=True) >>> # Example 2 - Display the middle x-slice of two 3D image stacks: >>> from timagetk.algorithms.exposure import global_contrast_stretch >>> stretch_img = global_contrast_stretch(image) >>> mid_x = image.get_shape('x')//2 >>> v = grayscale_imshow([image, stretch_img], mid_x, 'x', suptitle="Effect of global stretching") >>> # Example 3 - Display a z-axis projection of the 3D image: >>> v = grayscale_imshow(image, title=image.filename) """ def _to_2d(img, slice_id=None): if img.is2D(): return img, "" from timagetk.algorithms.reconstruction import image_surface_projection if slice_id is not None: max_slice = img.get_shape(axis) img = img.get_slice(slice_id, axis=axis) title_suffix = '{}-slice ({}/{})'.format(axis, slice_id, max_slice) else: img, altimap = image_surface_projection(img, threshold=kwargs.pop('threshold', None), orientation=kwargs.pop('orientation', 1)) title_suffix = '{}-axis projection'.format(axis) return img, title_suffix thumb_size = kwargs.pop('thumb_size', 5.) title = kwargs.pop('title', '') if isinstance(image, list): # Handles the title for each graph: if isinstance(title, str): titles = [title] * len(image) else: titles = title # Make sure we have a 2D image to plot: for n, img in enumerate(tqdm(image, unit="image")): image[n], title_suffix = _to_2d(img, slice_id) if title_suffix != "": titles[n] = f"{titles[n]} - {title_suffix}" if titles[n] != "" else title_suffix # Update kwargs to pass them to ``_multiple_plots`` kwargs.update({'val_range': val_range, 'cmap': cmap, 'title': titles}) return _multiple_plots(grayscale_imshow, image, **kwargs) else: image, title_suffix = _to_2d(image, slice_id) if title_suffix != "": title = f"{title} - {title_suffix}" if title != "" else title_suffix axe = kwargs.pop('axe', None) if axe is None: # Initialise figure: fig, ax_img = plt.subplots(figsize=[thumb_size, thumb_size]) else: fig = None # Use given plt.axe from plt.subplot(): ax_img = axe norm, bounds = None, None if isinstance(image, BlendImage): cmap = None val_range = [None, None] else: from matplotlib import colors if isinstance(cmap, str): if cmap == 'glasbey': from timagetk.visu.util import get_glasbey cmap = get_glasbey(np.unique(image), not_a_label=image.not_a_label, background=getattr(image, 'background', 1)) elif isinstance(cmap, colors.LinearSegmentedColormap): pass 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) from matplotlib.colors import BoundaryNorm norm = BoundaryNorm(bounds, cmap.N) interp = kwargs.get('interpolation', 'none') ax_img, fig_img = image_plot(image, ax_img, cmap=cmap, val_range=val_range, norm=norm, interpolation=interp) if kwargs.get('axis_off', False): plt.axis('off') if kwargs.get('no_xtick', False): fig_img.axes.xaxis.set_ticklabels([]) if kwargs.get('no_ytick', False): fig_img.axes.yaxis.set_ticklabels([]) if kwargs.get('no_xlab', False): fig_img.axes.xaxis.set_label_text("") if kwargs.get('no_ylab', False): fig_img.axes.yaxis.set_label_text("") ax_img.set_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 plt.colorbar(fig_img, ax=ax_img, ticks=bounds[::ticks_step][:-1] + 0.5, boundaries=bounds) else: plt.colorbar(fig_img, ax=ax_img, orientation="horizontal") # plt.tight_layout() subplots_adjust = kwargs.get('subplots_adjust', None) if subplots_adjust is not None: plt.subplots_adjust(**subplots_adjust) figname = kwargs.get('figname', "") # Save the figure if a 'figname' is given: if figname != "": plt.savefig(figname) elif kwargs.get('no_show', False): # Using 'no_show' keyword argument to build _multiple_plots, w return fig_img else: plt.show() return fig
[docs] def slice_n_hist(image, title="", img_title="", figname="", vmin=None, vmax=None, **kwargs): """Display a 2D image with value histogram and cumulative histogram. Parameters ---------- image : numpy.ndarray or timagetk.components.spatial_image.SpatialImage 2D image to represent title : str, optional If provided (default is empty), add this string of characters as title img_title : str, optional If provided (default is empty), add this string of characters as title figname : str, optional If provided (default is empty), the image will be saved under this filename. vmin : int or float, optional Minimum value to use in colormap vmax : int or float, optional Maximum value to use in colormap Other Parameters ---------------- bins: int The number of bins in the histogram. 256 by default. val_range : str or list of str, optional Define the range of values used by the colormap, by default *type* cmap : str or list of str, optional Colormap to use, see the ``stack_browser`` notes for advised colormaps See Also -------- skimage.exposure.histogram skimage.exposure.cumulative_distribution Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.mplt import slice_n_hist >>> fname = 'p58-t0-a0.lsm' >>> image_path = shared_data(fname, "p58") >>> img = imread(image_path) >>> # - Display the middle z-slice of the 3D image, with 'viridis' colormap: >>> mid_z = img.get_slice(img.get_shape('z')//2, 'z') >>> slice_n_hist(mid_z, title=img.filename, cmap='viridis') """ try: assert image.ndim == 2 except: raise ValueError("Input ``image`` should be 2D!") mini, maxi = type_to_range(image) if vmin is None: vmin = mini if vmax is None: vmax = maxi # Initialise figure: fig = plt.figure(constrained_layout=True) gs = fig.add_gridspec(2, 2, width_ratios=[6, 3], height_ratios=[1, 1], hspace=0.2) plt.suptitle(title) # Display 2D image: ax_img = fig.add_subplot(gs[:, 0]) ax_img, fig_img = image_plot(image, ax_img, val_range=[vmin, vmax], **kwargs) ax_img.set_title(img_title) # Plot intensity histogram ax_hist = fig.add_subplot(gs[0, 1]) # plt.hist(image.flatten(), bins=kwargs.get('bins', 256), range=(vmin, vmax + 1), normed=True) hist, hist_centers = histogram(image.get_array(), nbins=kwargs.get('bins', 256), normalize=True) ax_hist.plot(hist_centers, hist, lw=2) ax_hist.set_title('Intensity histogram') # Plot intensity cumulative histogram ax_chist = fig.add_subplot(gs[1, 1]) # plt.hist(image.flatten(), bins=kwargs.get('bins', 256), range=(vmin, vmax + 1), cumulative=True, # histtype='step', normed=True) chist, chist_centers = cumulative_distribution(image.get_array(), nbins=kwargs.get('bins', 256)) ax_chist.plot(chist_centers, chist, lw=2) ax_chist.set_title('Cumulative histogram') if figname != "": plt.savefig(figname) return
[docs] def plot_img_and_hist(image, cmap='gray', suptitle="", title="", **kwargs): """Plot an image along with its histogram and cumulative histogram. Example from .. [#] https://scikit-image.org/docs/stable/auto_examples/color_exposure/plot_local_equalize.html#sphx-glr-auto-examples-color-exposure-plot-local-equalize-py Parameters ---------- image : timagetk.components.spatial_image.SpatialImage 2D image to represent. cmap : str or list of str, optional Colormap to use, see the ``stack_browser`` notes for advised colormaps. suptitle : str, optional If provided (default is empty), add this string of characters as figure title. title : str, optional If provided (default is empty), add this string of characters as image title. Other Parameters ---------------- axe : plt.axes Two matplotlib axes to use to plot the image and the histogram. 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. data : numpy.ndarray An array of date used to create the histograms. threshold : None or float A theshold value to display on the histogram. 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. Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.visu.mplt import plot_img_and_hist >>> fname = 'p58-t0-a0.lsm' >>> image_path = shared_dataset("p58",'intensity')[0] >>> img = imread(image_path) >>> # - Display the middle z-slice of the 3D image, with 'viridis' colormap: >>> mid_z = img.get_slice(img.get_shape('z')//2, 'z') >>> ax_img, ax_hist, ax_cdf = plot_img_and_hist(mid_z, cmap='viridis') >>> # - Create a top z-axis projection to display the 3D image and show the 3D image histograms: >>> from timagetk.visu.projection import projection >>> proj = projection(img, method='contour', orientation=1) >>> ax_img, ax_hist, ax_cdf = plot_img_and_hist(proj, data=img) """ import matplotlib.ticker as ticker assert image.ndim == 2 thumb_size = kwargs.pop('thumb_size', 5.) axe = kwargs.get('axe', None) if axe is None: # Initialise figure: fig, axes = plt.subplots(ncols=2) fig.set_size_inches(h=thumb_size, w=thumb_size * 2) ax_img, ax_hist = axes else: ax_img, ax_hist = axe # Share x-axis for histogram and cumulative histogram ax_cdf = ax_hist.twinx() # Get minimal and maximal values available depending on image dtype: vmin, vmax = type_to_range(image) pv = "Pixels" if image.is2D() else "Voxels" # Display image ax_img, fig_img = image_plot(image, ax_img, cmap=cmap, val_range='type', colorbar=True) ax_img.set_title(title) fig.colorbar(fig_img, ax=axe, orientation='horizontal', location='bottom', label=f"{pv} intensity") fig.suptitle(suptitle) # Plot intensity histogram: data = kwargs.get('data', image.get_array()) data = data.flatten() ax_hist.hist(data, bins=min(vmax + 1, 256), range=(vmin, vmax + 1)) ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0)) ax_hist.set_xlabel(f"{pv} intensity") ax_hist.set_ylabel(f"Number of {pv.lower()}") if image.dtype == 'uint8': ax_hist.xaxis.set_major_locator(ticker.MultipleLocator(50)) ax_hist.xaxis.set_minor_locator(ticker.MultipleLocator(10)) elif image.dtype == 'uint16': ax_hist.xaxis.set_major_locator(ticker.MultipleLocator(50 * 255)) ax_hist.xaxis.set_minor_locator(ticker.MultipleLocator(10 * 255)) # Plot intensity cumulative histogram: ax_cdf.hist(data, bins=min(vmax + 1, 256), range=(vmin, vmax + 1), cumulative=True, histtype='step', density=True, color='red') ax_cdf.set_ylabel("Cumulative distribution") ax_hist.set_xlim(vmin, vmax) # Add a grid ax_hist.grid(which='both', linestyle=':') # Add the threshold vertical line if any: threshold = kwargs.get('threshold', None) if threshold is not None: ax_cdf.vlines([threshold], 0, 1, linestyles='dashdot', color='green') # Save the figure if a 'figname' is given: figname = kwargs.get('figname', "") if figname != "": plt.savefig(figname) else: plt.show() return ax_img, ax_hist, ax_cdf
[docs] def image_n_hist(image, cmap='gray', title="", img_title="", figname="", **kwargs): """Plot an image(s) along with its histogram and cumulative histogram. Parameters ---------- image : numpy.ndarray or timagetk.components.spatial_image.SpatialImage or list Image to use to generate the histogram, can be a list. cmap : str, optional Colormap to use, see the ``stack_browser`` notes for advised colormaps. title : str, optional If provided (default is empty), add this string of characters as title. img_title : str or list of str, optional If provided (default is empty), add this string of characters as title. figname : str, optional If provided (default is empty), the image will be saved under this filename. Other Parameters ---------------- thumb_size : float Image size in inch (default=5.) bins : integer or array_like If an integer is given, `bins + 1` bin edges are returned. Unequally spaced bins are supported if ``bins`` is a sequence. Defaults to ``256``. Notes ----- If a list of ``image`` is given, a list of ``img_title`` should also be given. See Also -------- timagetk.visu.stack.stack_browser for list of colormaps and some explanations Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.mplt import image_n_hist >>> from timagetk.algorithms.exposure import equalize_adapthist >>> image_path = shared_dataset("p58")[0] >>> image = imread(image_path) >>> ks_range = [1/12., 1/10., 1/8., 1/6.] >>> sh = image.shape[:2] >>> out_imgs = [equalize_adapthist(image,kernel_size=[ks*sh[0], ks*sh[1]]) for ks in ks_range] >>> subtitles = ["Original"] + ["Kernel-size {}".format(ks) for ks in ks_range] >>> image_n_hist([image.get_slice(50, 'z')] + [img.get_slice(50, 'z') for img in out_imgs], title="adaptive histogram equalization (z-slice=50/{})".format(image.shape[-1]), img_title=subtitles) """ thumb_size = kwargs.get('thumb_size', 5.) aspect_ratio = kwargs.get('xy_ratio', None) # Get grid shape depending on number of input 'image' to display: if isinstance(image, list): n_figs = len(image) # - Check 'img_title' if isinstance(img_title, list): assert len(img_title) == n_figs else: img_title = [img_title] * n_figs n_rows = 2 else: n_figs = 1 n_rows = 2 # Adjust the size of the figure according to the size of the grid plt.figure(figsize=[thumb_size * n_figs, thumb_size * n_rows]) plt.suptitle(title) axes = np.zeros((n_rows, n_figs), dtype=object) if isinstance(image, list): # - Create the grid with known number of rows and columns: gs = gridspec.GridSpec(n_rows, n_figs) # - Add each grayscale image to the grid: for n, img in enumerate(image): axes[0, n] = plt.subplot(gs[0, n]) # image (plt.imshow) axes[1, n] = plt.subplot(gs[1, n]) # histogram (plt.hist) ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, cmap=cmap, title=img_title[n], axe=axes[:, n], xy_ratio=aspect_ratio) else: axes[0, 0] = plt.subplot(n_rows, n_figs, 1) # image (plt.imshow) axes[1, 0] = plt.subplot(n_rows, n_figs, 2) # histogram (plt.hist) ax_img, ax_hist, ax_cdf = plot_img_and_hist(image, cmap=cmap, title=img_title, axe=axes[:, 0], xy_ratio=aspect_ratio) # Give more space at the top of the figure for suptitle: if title != "": plt.tight_layout(rect=[0.01, 0.01, .99, 0.93]) else: plt.tight_layout() # Save the figure if a 'figname' is given: if figname != "": plt.savefig(figname) else: plt.show() return
[docs] def plot_profiles(image, axe, x=None, y=None, z=None, title="", vmin=None, vmax=None): """Plot a profile of given line coordinates. The profile is the line at the intersection of the two planes. You thus need to specify two , and only two of the three axis. They act as numpy array index. Parameters ---------- axe title image : timagetk.components.spatial_image.SpatialImage Image to use to plot the profile. x : int, optional X-axis plane coordinates. y : int, optional Y-axis plane coordinates. z : int, optional Z-axis plane coordinates. vmin : int | float, optional Minimum value to use in colormap. vmax : int | float, optional Maximum value to use in colormap. Examples -------- >>> import matplotlib.pyplot as plt >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.mplt import plot_profiles >>> image_path = shared_dataset("p58")[0] >>> image = imread(image_path) >>> plt.figure(figsize=[9., 5.]) >>> ax = plt.subplot() >>> # Plot the original profile and the equalized one: >>> ax = plot_profiles(image, ax, x=int(image.get_shape('x')/2.), z=40, title="original") >>> from timagetk.algorithms.exposure import equalize_adapthist >>> eq_img = equalize_adapthist(image) >>> ax = plot_profiles(eq_img, ax, x=int(image.get_shape('x')/2.), z=40, title="equalized") >>> from timagetk.algorithms.linearfilter import gaussian_filter >>> std = 1. >>> smooth_img = gaussian_filter(image, sigma=std, real=True) >>> ax = plot_profiles(smooth_img, ax, x=int(image.get_shape('x')/2.), z=40, title="smoothed({}um)".format(std)) >>> eq_smooth_img = equalize_adapthist(gaussian_filter(image, sigma=std, real=True)) >>> ax = plot_profiles(eq_smooth_img, ax, x=int(image.get_shape('x')/2.), z=40, title="equalized & smoothed({}um)".format(std)) >>> plt.legend() >>> plt.show() """ assert sum([axis is None for axis in [x, y, z]]) == 1 from skimage.util.dtype import dtype_range xmin, xmax = dtype_range[image.dtype.type] if vmin is None: vmin = xmin if vmax is None: vmax = xmax # plt.figure(figsize=[9., 5.]) xs, ys, zs = image.shape if x is None: profile = image.get_array()[:, y, z] y_label = "Y: {}/{}; Z:{}/{}".format(y, ys, z, zs) elif y is None: profile = image.get_array()[x, :, z] y_label = "X: {}/{}; Z:{}/{}".format(x, xs, z, zs) else: profile = image.get_array()[x, y, :] y_label = "X: {}/{}; Y:{}/{}".format(x, xs, y, ys) ax_line = _profiles(axe, profile, title, y_label, vmin, vmax) return ax_line
def _profiles(axe, profile, legend_label, y_label, vmin, vmax): """Profile sub-function. Parameters ---------- axe : plt.axes Matplotlib axes to use. profile : numpy.ndarray A profile to plot, see ``plot_profile`` for profile definition. legend_label : str, Label of the profile, to use with 'plt.legend()'. y_label : str, Label to add to y-axis. vmin : int or float, optional Minimum value to use in colormap. vmax : int or float, optional Maximum value to use in colormap. Returns ------- plt.axes Matplotlib axes """ lp = len(profile) ax_line = axe ax_line.plot(range(lp), profile, label=legend_label) ax_line.set_xlim(0, lp) ax_line.set_ylim(vmin, vmax) ax_line.set_xlabel(y_label) ax_line.set_ylabel('Voxel intensity') return ax_line
[docs] def profile_details(image, x=None, y=None, z=None, title="", vmin=None, vmax=None, plane="z", figname="", **kwargs): """Plot a profile of given line coordinates and an image zoom-in around it. The profile is the line at the intersection of the two planes. You thus need to specify two , and only two of the three axis. They act as numpy array index. The 'plane' define which one will be used for the image display. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage Image to use to plot the profile. x : int, optional X-axis plane coordinates. y : int, optional Y-axis plane coordinates. z : int, optional Z-axis plane coordinates. title : str, optional Title to give to the figure. vmin : int | float, optional Minimum value to use in colormap. vmax : int | float, optional Maximum value to use in colormap. plane : str, optional Plane to use for image display. figname : str, optional If provided (default is empty), the image will be saved under this filename. Other Parameters ---------------- zone : int Width of the zone around the line. cmap : str Colormap to use, see the notes of ``stack_browser`` for advised colormaps. Examples -------- >>> import numpy as np >>> import matplotlib.pyplot as plt >>> from matplotlib import gridspec >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.mplt import profile_details >>> image_path = shared_dataset("p58")[0] >>> image = imread(image_path) >>> # Plot the profile details: >>> profile_details(image, x=int(image.get_shape('x')/2.), z=40, title="original") """ plt.figure(figsize=[9., 5.]) plt.suptitle(title) nrow = 2 gs = gridspec.GridSpec(nrow, 1, height_ratios=[1, 8]) gs.update(hspace=0.01) axes = [plt.subplot(gs[n, 0]) for n in range(nrow)] axes = _profile_details(image, axes, x, y, z, "", vmin, vmax, plane, **kwargs) # Save the figure if a 'figname' is given: if figname != "": plt.savefig(figname) else: plt.show()
def _profile_details(image, axes, x=None, y=None, z=None, title="", vmin=None, vmax=None, plane="z", **kwargs): """Plot a profile of given line coordinates and an image zoom-in around it. The profile is the line at the intersection of the two planes. You thus need to specify two , and only two of the three axis. They act as numpy array index. The 'plane' define which one will be used for the image display. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage Image to use to plot the profile. axes : plt.axes Matplotlib axes to use. x : int, optional X-axis plane coordinates. y : int, optional Y-axis plane coordinates. z : int, optional Z-axis plane coordinates. vmin : int | float, optional Minimum value to use in colormap. vmax : int | float, optional Maximum value to use in colormap. plane : str, optional Plane to use for image display. Other Parameters ---------------- zone : int Width of the zone around the line. cmap : str Colormap to use, see the notes of ``stack_browser`` for advised colormaps. Returns ------- """ zone = kwargs.get('zone', 5) cmap = kwargs.get('camp', 'gray') assert sum([axis is None for axis in [x, y, z]]) == 1 ax_img, ax_line = axes from skimage.util.dtype import dtype_range xmin, xmax = dtype_range[image.dtype.type] if vmin is None: vmin = xmin if vmax is None: vmax = xmax # plt.figure(figsize=[9., 5.]) xs, ys, zs = image.shape if x is None: if plane == 'y': arr = image.get_array()[:, y, z - zone:z + zone] else: arr = image.get_array()[:, y - zone:y + zone, z] plane = "z" profile = image.get_array()[:, y, z] y_label = "Y: {}/{}; Z:{}/{}".format(y, ys, z, zs) elif y is None: if plane == 'x': arr = image.get_array()[x, :, z - zone:z + zone] else: arr = image.get_array()[x - zone:x + zone, :, z] plane = "z" profile = image.get_array()[x, :, z] y_label = "X: {}/{}; Z:{}/{}".format(x, xs, z, zs) else: if plane == 'x': arr = image.get_array()[x, y - zone:y + zone, :] else: arr = image.get_array()[x - zone:x + zone, y, :] plane = "y" profile = image.get_array()[x, y, :] y_label = "X: {}/{}; Y:{}/{}".format(x, xs, y, ys) if plane == 'x': aspect_ratio = image.extent[1] / image.extent[2] elif plane == 'y': aspect_ratio = image.extent[0] / image.extent[2] else: aspect_ratio = image.extent[0] / image.extent[1] ax_line = _profiles(ax_line, profile, title, y_label, vmin, vmax) ax_img.plot([0, arr.shape[1]], [zone - 1, zone - 1], color='red') ax_img.imshow(arr, cmap=cmap, vmin=vmin, vmax=vmax, aspect=aspect_ratio, interpolation='none') ax_img.set_xticks([]) ax_img.set_yticks([]) ax_img.set_ylabel("{}-plane".format(plane.upper()), rotation=0, verticalalignment='center', horizontalalignment='right') return ax_img, ax_line def _get_zone_array(image, x, y, z, plane, zone): """Get a 2D sub-SpatialImage from a 3D SpatialImage from given coordinates and plane. Parameters ``plane`` and ``zone`` indicate the plane and the sampling around the given coordinate of this plane. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage Image from which the sub-array should be taken from. x : int, None X-axis plane coordinates. y : int, None Y-axis plane coordinates. z : int, None Z-axis plane coordinates. plane : str Plane to use for image display. zone : int Width of the zone around the line. Returns ------- timagetk.components.spatial_image.SpatialImage Selected 2D sub-array, with the second axis of dimension ``zone * 2``. Examples -------- >>> import numpy as np >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.mplt import _get_zone_array >>> fname = 'p58-t0-a0.lsm' >>> image_path = shared_data(fname, "p58") >>> image = imread(image_path) >>> mid_x = int(image.get_shape('x')/2.) >>> mid_y = int(image.get_shape('y')/2.) >>> mid_z = int(image.get_shape('z')/2.) >>> arr = _get_zone_array(image, x=mid_x, y=mid_y, z=None, plane='x', zone=5) >>> print(arr.shape, arr.voxelsize) >>> arr = _get_zone_array(image, x=mid_x, y=mid_y, z=None, plane='y', zone=5) >>> print(arr.shape, arr.voxelsize) >>> arr = _get_zone_array(image, x=mid_x, y=None, z=mid_z, plane='x', zone=5) >>> print(arr.shape, arr.voxelsize) >>> arr = _get_zone_array(image, x=mid_x, y=None, z=mid_z, plane='z', zone=5) >>> print(arr.shape, arr.voxelsize) >>> arr = _get_zone_array(image, x=None, y=mid_y, z=mid_z, plane='y', zone=5) >>> print(arr.shape, arr.voxelsize) >>> arr = _get_zone_array(image, x=None, y=mid_y, z=mid_z, plane='z', zone=5) >>> print(arr.shape, arr.voxelsize) """ vx, vy, vz = image.voxelsize if x is None: if plane == 'y': arr = image.get_array()[:, y, z - zone:z + zone] arr = SpatialImage(arr, voxelsize=[vx, vz]) else: # z-plane arr = image.get_array()[:, y - zone:y + zone, z] arr = SpatialImage(arr, voxelsize=[vx, vy]) elif y is None: if plane == 'x': arr = image.get_array()[x, :, z - zone:z + zone] arr = SpatialImage(arr, voxelsize=[vy, vz]) # arr = SpatialImage(arr, voxelsize=[vy, vz]) else: # z-plane arr = image.get_array()[x - zone:x + zone, :, z].transpose((1, 0)) arr = SpatialImage(arr, voxelsize=[vy, vx]) else: if plane == 'x': arr = image.get_array()[x, y - zone:y + zone, :].transpose((1, 0)) arr = SpatialImage(arr, voxelsize=[vz, vy]) else: # y-plane arr = image.get_array()[x - zone:x + zone, y, :].transpose((1, 0)) arr = SpatialImage(arr, voxelsize=[vz, vx]) return arr
[docs] def profile_hmin(image, x=None, y=None, z=None, title="", intensity_range='auto', plane="z", **kwargs): """Plot a profile of given line coordinates and an image zoom-in around it. The profile is the line at the intersection of the two planes. You thus need to specify two , and only two of the three axis. They act as numpy array index. The 'plane' define which one will be used for the image display. Parameters ---------- image : timagetk.components.spatial_image.SpatialImage Image to use to plot the profile x : int, optional X-axis plane coordinates. y : int, optional Y-axis plane coordinates. z : int, optional Z-axis plane coordinates. title : str, optional Title to give to the figure, *e.g.* the file name, default is empty. intensity_range : str | list, optional Define the range of values used by the colormap, by default 'type'. plane : str, optional Plane to use for image display. Other Parameters ---------------- zone : int Width of the zone around the line. cmap : str Colormap to use, see the notes of ``stack_browser`` for advised colormaps. no_show : bool If True, do not call `plt.show()` automatically. hmin_max : int Change the slider max value of h-minima. hmin_init : int Change the slider initial value of h-minima. hmin_step : int Change the slider step value of h-minima. Examples -------- >>> import numpy as np >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.visu.mplt import profile_hmin >>> fname = 'p58-t0-a0.lsm' >>> image_path = shared_data(fname, "p58") >>> image = imread(image_path) >>> zslice = 40 >>> x_coord = int(image.get_shape('x')/2.) >>> # - Interactive 'h-min' search on 'original' image: >>> profile_hmin(image, x=x_coord, z=zslice, title=fname) >>> # - Interactive 'h-min' search on 'smoothed' image: >>> from timagetk.algorithms.linearfilter import gaussian_filter >>> smooth_img = gaussian_filter(image, sigma=1., real=True) >>> profile_hmin(smooth_img, x=x_coord, z=zslice, title=fname+" - smoothed") >>> # - Interactive 'h-min' search on 'isometric & smoothed' image: >>> from timagetk.algorithms.linearfilter import gaussian_filter >>> from timagetk.algorithms.resample import isometric_resampling >>> iso_smooth_img = gaussian_filter(isometric_resampling(image), sigma=0.3, real=True) >>> iso_zslice = iso_smooth_img.get_shape('z') / image.get_shape('z') * zslice >>> profile_hmin(iso_smooth_img, x=x_coord, z=iso_zslice, title=fname+" - isometric & smoothed") >>> # - Interactive 'h-min' search on 'isometric, smoothed & equalized' image: >>> from timagetk.algorithms.exposure import equalize_adapthist >>> iso_smooth_eq_img = equalize_adapthist(iso_smooth_img) >>> profile_hmin(iso_smooth_eq_img, x=x_coord, z=iso_zslice, title=fname+" - isometric, smoothed & equalized'") """ zone = kwargs.get('zone', 10) cmap = kwargs.get('cmap', 'gray') if image.dtype == np.uint8: hmin_init = 5 hmin_max = 45 hmin_step = 1 else: hmin_init = 500 hmin_max = 4500 hmin_step = 100 hmin_max = kwargs.get('hmin_max', hmin_max) hmin_init = kwargs.get('hmin_init', hmin_init) hmin_step = kwargs.get('hmin_step', hmin_step) xs, ys, zs = image.shape if x is None: if plane != 'y': plane = "z" profile = image.get_array()[:, y, z] y_label = "Y: {}/{}; Z:{}/{}".format(y, ys, z, zs) elif y is None: if plane != 'x': plane = "z" profile = image.get_array()[x, :, z] y_label = "X: {}/{}; Z:{}/{}".format(x, xs, z, zs) else: if plane != 'x': plane = "y" profile = image.get_array()[x, y, :] y_label = "X: {}/{}; Y:{}/{}".format(x, xs, y, ys) # - Compute segmented sub-image: subimg = _get_zone_array(image, x, y, z, plane, zone) aspect_ratio = subimg.extent[0] / subimg.extent[1] if isinstance(intensity_range, str): intensity_range = convert_str_range(subimg, intensity_range) else: assert isinstance(intensity_range, list) and len(intensity_range) == 2 mini, maxi = intensity_range # - h-transform: ext_img = regional_extrema(subimg, height=hmin_init, method='min', connectivity=8) if ext_img is None: ext_img = np.zeros_like(subimg.get_array()) # - connexe component labelling: seed_img = connected_components(ext_img, low_threshold=1, high_threshold=hmin_init, connectivity=8) n_seeds = len(np.unique(seed_img)) - 1 # '0' is in the list! log.info("Detected {} seeds!".format(n_seeds)) if seed_img is None: seed_img = np.zeros_like(subimg.get_array()) # - watershed segmentation: seg_img = watershed(subimg, seed_img) if seg_img is None: seg_img = np.zeros_like(subimg) # - blend intensity and labelled image (return a numpy array): blend = label_blending(seg_img, subimg) # -- Initialise figure fig = plt.figure(figsize=[12., 6.]) plt.subplots_adjust(bottom=0.1) plt.suptitle(title) nrow = 5 gs = gridspec.GridSpec(nrow, 1) gs.update(hspace=0.01) ax_img, ax_ext, ax_seg, ax_blend, ax_line = [plt.subplot(gs[n, 0]) for n in range(nrow)] # - Plot intensity sub-image: ax_img.plot([0, subimg.get_shape('x')], [zone - 1, zone - 1], color='red') ax_img.imshow(subimg.get_array().transpose((1, 0)), cmap=cmap, vmin=mini, vmax=maxi, interpolation='none') ax_img.set_ylabel("{}-plane".format(plane.upper()), rotation=0, verticalalignment='center', horizontalalignment='right') # - Plot detected regional-minima in sub-image: ax_ext.plot([0, subimg.get_shape('x')], [zone - 1, zone - 1], color='black') ext_fig = ax_ext.imshow(ext_img.get_array().transpose((1, 0)), cmap='viridis', interpolation='none', vmin=0, vmax=np.max(ext_img)) ax_ext.set_ylabel("h-minima", rotation=0, verticalalignment='center', horizontalalignment='right') # - Plot segmented sub-image: ax_seg.plot([0, subimg.get_shape('x')], [zone - 1, zone - 1], color='black') seg_fig = ax_seg.imshow(seg_img.get_array().transpose((1, 0)), cmap='Set1', interpolation='none', vmin=0, vmax=np.max(seed_img)) ax_seg.set_ylabel("segmentation", rotation=0, verticalalignment='center', horizontalalignment='right') # - Plot blending of intensity & segmented sub-image: ax_blend.plot([0, subimg.get_shape('x')], [zone - 1, zone - 1], color='black') blend_fig = ax_blend.imshow(blend.transpose((1, 0, 2)), interpolation='none') # blend_fig = ax_blend.imshow(seg_img.get_array().transpose((1, 0)), cmap='Set1', aspect=aspect_ratio, # interpolation='none') ax_blend.set_ylabel("blending", rotation=0, verticalalignment='center', horizontalalignment='right') # Force correct aspect ratio of image and remove x & y ticks: for ax in [ax_img, ax_ext, ax_seg, ax_blend]: _force_aspect_ratio(ax, aspect_ratio) ax.set_xticks([]) ax.set_yticks([]) # - Plot profile: ax_line = _profiles(ax_line, profile, "profile", y_label, mini, maxi) # - Plot the h-extrema: hext_fig, = ax_line.plot(range(len(profile)), ext_img[:, zone - 1], label="h-extrema") from timagetk.visu.stack import slider hs = slider(label='h-minima', mini=1, maxi=hmin_max, init=hmin_init, step=hmin_step, fmt="%1.0f") def _update(val): hmin = hs.val # - h-transform: ext_img = regional_extrema(subimg, height=hmin, method='min') if ext_img is None: ext_img = np.zeros_like(subimg) # - connexe component labelling: seed_img = connected_components(ext_img, low_threshold=1, high_threshold=hmin) n_seeds = len(np.unique(seed_img)) - 1 # '0' is in the list! log.info("Detected {} seeds!".format(n_seeds)) if seed_img is None: seed_img = np.zeros_like(subimg) # - watershed segmentation: seg_img = watershed(subimg, seed_img) if seg_img is None: seg_img = np.zeros_like(subimg) # - blend intensity and labelled image: blend = label_blending(seg_img, subimg) # - Update figures: ext_fig.set_data(ext_img.get_array().transpose((1, 0))) hext_fig.set_ydata(ext_img[:, zone - 1]) seg_fig.set_data(seg_img.get_array().transpose((1, 0))) seg_fig.set_clim([np.min(seg_img.get_array()), np.max(seg_img.get_array())]) blend_fig.set_data(blend.transpose((1, 0, 2))) fig.canvas.draw_idle() hs.on_changed(_update) ax_line.legend(fontsize='small', framealpha=0.2, frameon=True) if kwargs.get('no_show', False): return None else: plt.show() return int(hs.val)
# def segmented_profiles(image, axe=None, x=None, y=None, z=None, hmin=20, # title="", vmin=None, # vmax=None): # """Plot a profile of given line coordinates. # # The profile is the line at the intersection of the two planes. # You thus need to specify two , and only two of the three axis. # They act as numpy array index. # # Parameters # ---------- # image : timagetk.components.spatial_image.SpatialImage # Image to use to plot the profile # x : int, optional # x-axis plane coordinates # y : int, optional # x-axis plane coordinates # z : int, optional # x-axis plane coordinates # vmin : int | float, optional # minimum value to use in colormap # vmax : int | float, optional # maximum value to use in colormap # # Examples # -------- # >>> import matplotlib.pyplot as plt # >>> from timagetk.io import imread # >>> from timagetk.io.util import shared_data # >>> from timagetk.visu.mplt import plot_profiles # >>> image_path = shared_dataset("p58")[0] # >>> image = imread(image_path) # # >>> plt.figure(figsize=[9., 5.]) # >>> # Plot the original profile and the equalized one: # >>> ax = plot_profiles(image, x=int(image.get_shape('x')/2.), z=40, title="original") # >>> from timagetk.algorithms.exposure import z_slice_equalize_adapthist # >>> eq_img = z_slice_equalize_adapthist(image) # >>> ax = plot_profiles(eq_img, ax, x=int(image.get_shape('x')/2.), z=40, title="equalized") # >>> from timagetk.plugins import linear_filtering # >>> std = 1. # >>> smooth_img = linear_filtering(image, method="gaussian_smoothing", sigma=std, real=True) # >>> ax = plot_profiles(smooth_img, ax, x=int(image.get_shape('x')/2.), z=40, title="smoothed({}um)".format(std)) # >>> eq_smooth_img = z_slice_equalize_adapthist(linear_filtering(image, method="gaussian_smoothing", sigma=std, real=True)) # >>> ax = plot_profiles(eq_smooth_img, ax, x=int(image.get_shape('x')/2.), z=40, title="equalized & smoothed({}um)".format(std)) # >>> plt.legend() # >>> plt.show() # # Returns # ------- # # """ # assert sum([axis is None for axis in [x, y, z]]) == 1 # from skimage.util.dtype import dtype_range # # xmin, xmax = dtype_range[image.dtype.type] # if vmin is None: # vmin = xmin # if vmax is None: # vmax = xmax # # # plt.figure(figsize=[9., 5.]) # xs, ys, zs = image.shape # if x is None: # profile = image.get_array()[:, y, z] # y_label = "Y: {}/{}; Z:{}/{}".format(y, ys, z, zs) # elif y is None: # profile = image.get_array()[x, :, z] # y_label = "X: {}/{}; Z:{}/{}".format(x, xs, z, zs) # else: # profile = image.get_array()[x, y, :] # y_label = "X: {}/{}; Y:{}/{}".format(x, xs, y, ys) # # if axe is None: # axe = plt.subplot() # # x = range(len(profile)) # y = profile # seg_profile = seg_img[zone - 1, :] # # points = np.array([x, y]).T.reshape(-1, 1, 2) # segments = np.concatenate([points[:-1], points[1:]], axis=1) # # norm = plt.Normalize(seg_profile.min(), seg_profile.max()) # lc = LineCollection(segments, cmap='Set1', norm=norm) # lc.set_array(seg_profile) # lc.set_linewidth(2) # lc.set_label('segmented profile') # segline = ax_blendline.add_collection(lc) # print("here {}".format(n)) # # n += 1 # # return ax_line
[docs] def intensity_histogram(image, slice_id=None, axis="z", **kwargs): """Intensity distribution as histogram and cumulative histogram. Uses ``matplotlib.hist()`` and ``_multiple_plots`` if more than one `image` is given. Parameters ---------- image : numpy.ndarray or timagetk.components.spatial_image.SpatialImage Image to use to generate the histogram, can also be a list. slice_id : int, optional Use all slices by default (None), else the z-slice to get for the histogram. axis : str, optional Axis to use when specifying a `slice_id`, ``'z'`` by default. Other Parameters ---------------- title : list of str A list of titles to use as sub-figures titles, usually used when a list of images is given. suptitle : str A general title placed above the sub-figures titles, usually used when a list of images is given. axe : plt.axes Matplotlib axes to use with ``_multiple_plots``, then ``no_show`` should be ``True``. max_per_line : int Number of figure per line when using more than one images, default is ``4``. thumb_size : float Image size in inch, default is ``5``. no_show : bool If ``True``do not call the blocking 'show' method. Default is ``False``. no_left_ylab : bool If ``True``remove the left y-axis label. Default is ``False``. no_right_ylab : bool If ``True``remove the right y-axis label. Default is ``False``. Examples -------- >>> from timagetk.io.util import shared_data >>> from timagetk.io import imread >>> from timagetk.visu.mplt import intensity_histogram >>> import matplotlib.pyplot as plt >>> img = imread(shared_data("p58-t0-a0.lsm", "p58")) >>> intensity_histogram(img) >>> intensity_histogram(img, slice_id=5, axis="z") >>> from timagetk.algorithms.exposure import global_contrast_stretch >>> stretch_img = global_contrast_stretch(img) >>> intensity_histogram([img, stretch_img], suptitle="Effect of global stretching on intensity distribution") """ if isinstance(image, list): return _multiple_plots(intensity_histogram, image, cbar=False, **kwargs) thumb_size = kwargs.pop('thumb_size', 5.) axe = kwargs.get('axe', None) if axe is None: # Initialise figure: fig, ax_hist = plt.subplots() fig.set_size_inches(w=thumb_size, h=thumb_size) else: # Use given plt.axe from plt.subplot(): ax_hist = axe # Share x-axis for histogram and cumulative histogram ax_cdf = ax_hist.twinx() # Get minimal and maximal values available depending on image dtype: vmin, vmax = type_to_range(image) if slice_id is not None: max_slice = image.get_shape(axis) image = image.get_slice(slice_id, axis=axis) title_suffix = '{}-slice ({}/{})'.format(axis, slice_id, max_slice) else: title_suffix = "" pv = "Pixels" if image.is2D() else "Voxels" # Plot intensity histogram: hist_fig = ax_hist.hist(image.flatten(), bins=kwargs.get('bins', 256), range=(vmin, vmax + 1)) ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0)) ax_hist.set_xlabel("{} intensity".format(pv)) if not kwargs.get('no_left_ylab', False): ax_hist.set_ylabel("Number of {}".format(pv.lower())) # Plot intensity cumulative histogram: ax_cdf.hist(image.flatten(), bins=kwargs.get('bins', 256), range=(vmin, vmax + 1), cumulative=True, histtype='step', density=True, color='red') if not kwargs.get('no_right_ylab', False): ax_cdf.set_ylabel("Cumulative distribution") ax_hist.set_xlim(vmin, vmax) title = kwargs.get('title', '') if title == '': try: title = image.filename except AttributeError: pass if title == '': title += title_suffix elif title_suffix != '': title += " - " + title_suffix ax_hist.set_title(title) if title != "": plt.subplots_adjust(top=0.95) else: plt.subplots_adjust(top=0.99) figname = kwargs.get('figname', "") # Save the figure if a 'figname' is given: if figname != "": plt.savefig(figname) return None elif kwargs.get('no_show', False): return hist_fig, 1. else: plt.show() return None
def _multiple_plots(plotting_func, image, **kwargs): """Iterate `plotting_func` to match number of given `image`. Parameters ---------- plotting_func : func Plotting function to iterate. image : list of timagetk.components.spatial_image.SpatialImage List of image to which to apply the plotting function. Other Parameters ---------------- title : str, optional If provided (default is empty), add this string of characters as title. suptitle : str A general title placed above the sub-figures titles, usually used when a list of images is given. max_per_line : int Maximum number of figure per line (default=4). thumb_size : float Image size in inch (default=5.). Returns ------- matplotlib.pyplot.figure.Figure A matplotlib figure """ assert isinstance(image, list) n_figs = len(image) suptitle = kwargs.pop('suptitle', '') figname = kwargs.pop('figname', '') no_show = kwargs.pop('no_show', False) thumb_size = kwargs.pop('thumb_size', 5.) max_per_line = int(kwargs.pop('max_per_line', 4)) title = kwargs.pop('title', None) if title: assert len(title) == n_figs val_range = kwargs.pop('val_range', None) if isinstance(val_range, list) and not isinstance(val_range[0], int): assert len(val_range) == n_figs elif val_range: val_range = [val_range] * n_figs # Control 'threshold', used by `grayscale_imshow`, cmap = kwargs.pop('cmap', None) if isinstance(cmap, list): assert len(cmap) == n_figs elif cmap: cmap = [cmap] * n_figs # Control 'threshold', used by `grayscale_imshow` for `contour_projection` threshold = kwargs.pop('threshold', None) if isinstance(threshold, list): assert len(threshold) == n_figs elif threshold: threshold = [threshold] * n_figs # Control 'colorbar', used by `grayscale_imshow`, if defined manually, no auto-control if 'cbar' not in kwargs: all_same_range = np.alltrue([vrange == val_range[0] for vrange in val_range]) all_same_cmap = np.alltrue([cm == cmap[0] for cm in cmap]) if all_same_range and all_same_cmap: # Colorbars are only displayed for the last figure if value range is always the same: kwargs.update({'cbar': False}) else: # If auto-scaling or specified list of value range for colorbar, need to always have it kwargs.update({'cbar': True}) else: all_same_range = False all_same_cmap = False # - Get the number of figure to display per rows, *i.e.* the number of columns: n_columns = min([n_figs, max_per_line]) # - Get the number of rows: n_rows = n_figs // max_per_line n_rows += 1 if n_rows != (n_figs / max_per_line) else 0 ref_extent = image[0].get_extent() same_extent = all(im.get_extent() == ref_extent for im in image) try: ref_height, ref_width = image[0].get_extent() except ValueError: ref_height, ref_width, _ = image[0].get_extent() same_width = all(im.get_extent()[1] == ref_width for im in image) if not same_width: max_width = max([im.get_extent()[1] for im in image]) width_ratios = [thumb_size * im.get_extent()[1] / max_width for im in image] else: width_ratios = [thumb_size] * len(image) hw_ratio = [np.divide(*im.get_extent()) for im in image] max_hw_ratio = max(hw_ratio) if max_hw_ratio > 1.: thumb_height = max([hwr * thumb_size for hwr in hw_ratio]) else: thumb_height = thumb_size fig, axes = plt.subplots(n_rows, n_columns, gridspec_kw={'width_ratios': width_ratios}) if n_rows == 1 or n_columns == 1: axes = axes.reshape(n_rows, n_columns) plt.suptitle(suptitle) id_col, id_row = 0, 0 sub_figs = [] for n in range(n_figs): img = image[n] if title is not None: kwargs['title'] = title[n] if cmap is not None: kwargs['cmap'] = cmap[n] if threshold is not None: kwargs['threshold'] = threshold[n] if val_range: kwargs['val_range'] = val_range[n] # Left y-label are displayed only for the first plot of each row: first_col = id_col == 0 kwargs['no_ylab'] = True if not first_col else False # Bottom x-label are displayed only for the last row: first_row = id_row == 0 last_row = id_row == n_rows - 1 kwargs['no_xlab'] = True if not last_row else False if same_extent: # If all images have the same extent we can remove the Y-ticks for images after the first one (per row) kwargs['no_ytick'] = True if not first_col else False # If all images have the same extent we can remove the X-ticks for images after the first one (per columns) kwargs['no_xtick'] = True if not first_row else False # Call plotting function: s_fig = plotting_func(img, axe=axes[id_row, id_col], no_show=True, **kwargs) sub_figs.append(s_fig) if id_col == max_per_line - 1: id_col = 0 id_row += 1 else: id_col += 1 h_offset = 0.5 if suptitle != "": h_offset += 0.5 # save some inches to display suptitle if all_same_range and all_same_cmap: plt.colorbar(sub_figs[0], ax=axes, orientation="horizontal", fraction=.1, aspect=40) h_offset += 0.8 # save some inches to display colorbar fig.set_size_inches(w=thumb_size * n_columns, h=(thumb_height * n_rows) + h_offset) # plt.tight_layout() # Give more space at the top of the figure for suptitle: if suptitle != "" or title: plt.subplots_adjust(top=0.95) else: plt.subplots_adjust(top=0.99) if plotting_func.__name__ == 'grayscale_imshow': if all_same_range and all_same_cmap: # Case where we put a colorbar at the bottom below all subplots: plt.subplots_adjust(bottom=0.2) plt.subplots_adjust(hspace=0.2) else: plt.subplots_adjust(bottom=0.1) plt.subplots_adjust(hspace=0.2) plt.subplots_adjust(wspace=0.09) # Save the figure if a 'figname' is given: if figname != "": plt.savefig(figname) plt.close() elif no_show: pass else: plt.show() return fig, axes