Source code for sdt.nbui.image_display

# SPDX-FileCopyrightText: 2020 Lukas Schrangl <lukas.schrangl@tuwien.ac.at>
#
# SPDX-License-Identifier: BSD-3-Clause

"""Provide the :py:class:`ImageDisplay` class for displaying images"""
import threading
from typing import Optional, Tuple, Union

import ipywidgets
import matplotlib as mpl
import numpy as np
import traitlets


def _guess_img_scale_limits(img: np.ndarray) -> Tuple[float, float]:
    """Infer potential minimum and maximum values from the datatype

    E.g., uint8 means minimum is 0 and maximum is 255. This is harder for
    floats: They could be scaled between 0 and 1 or integer images converted
    to float and thus have arbitrary values. In this case, inspect the actual
    image data and try to guess from the minimum and maximum.

    Paramters
    ---------
    img
        Image data

    Returns
    -------
    Range minimum and maximum
    """
    if np.issubdtype(img.dtype, np.floating):
        # ugly hack; get min and max corresponding to integer types based
        # on the range of values in the first image
        min = img.min()
        if min < 0:
            types = (np.int8, np.int16, np.int32, np.int64)
        else:
            types = (np.uint8, np.uint16, np.uint32, np.uint64)
        max = img.max()
        if min >= 0. and max <= 1.:
            min = 0
            max = 1
        else:
            for t in types:
                ii = np.iinfo(t)
                if min >= ii.min and max <= ii.max:
                    min = ii.min
                    max = ii.max
                    break
    else:
        min = np.iinfo(img.dtype).min
        max = np.iinfo(img.dtype).max
    return min, max


[docs]class ImageDisplay(ipywidgets.VBox): """Widget to display an image Additionally, a slider to set black and white points is provided. """ input: Union[np.ndarray, None] = \ traitlets.Instance(np.ndarray, allow_none=True) """Image array to display""" ax: mpl.axes.Axes """Axes instance to use for plotting""" cmap: Union[str, mpl.colors.Colormap] """Colormap for image display""" def __init__(self, ax: mpl.axes.Axes, input: Optional[np.ndarray] = None, cmap: Union[str, mpl.colors.Colormap] = "gray", **kwargs): """Parameters ---------- ax Axes instance to use for plotting input Image array to display cmap Colormap for image display **kwargs Passed to parent ``__init__``. """ self.ax = ax self.cmap = cmap self._img_artist = None self._img_scale_sel = ipywidgets.IntRangeSlider( min=0, max=1, layout=ipywidgets.Layout(width="75%"), description="contrast" ) self._img_scale_sel.observe(self._redraw_image, names="value") self._auto_scale_button = ipywidgets.Button(description="Auto") self._auto_scale_button.on_click(self.auto_scale) super().__init__([ self.ax.figure.canvas, ipywidgets.HBox([self._img_scale_sel, self._auto_scale_button])], **kwargs) self._redraw_lock = threading.Lock() self.input = input @traitlets.observe("input") def _input_changed(self, change=None): """Callback for change of `input` traitlet""" if self.input is not None: lim = _guess_img_scale_limits(self.input) with self._redraw_lock: self._img_scale_sel.min = lim[0] self._img_scale_sel.max = lim[1] self._redraw_image() def _redraw_image(self, change=None): """Redraw the image""" if self._redraw_lock.locked(): return if self._img_artist is not None: self._img_artist.remove() scale = self._img_scale_sel.value if self.input is not None: self._img_artist = self.ax.imshow( self.input, cmap=self.cmap, vmin=scale[0], vmax=scale[1]) else: self._img_artist = None self.ax.figure.canvas.draw_idle()
[docs] def auto_scale(self, b=None): """Auto-set black and white points Set black point to the minimum and white point to the maximum value of the image data (:py:attr:`input` attribute). """ if self.input is None: return self._img_scale_sel.value = (self.input.min(), self.input.max())