Source code for plotpy.items.image.standard

# -*- coding: utf-8 -*-
# pylint: disable=C0103

from __future__ import annotations

import sys
from typing import TYPE_CHECKING

import numpy as np
from guidata.utils.misc import assert_interfaces_valid
from qtpy import QtCore as QC

from plotpy import io
from plotpy.config import _
from plotpy.constants import LUTAlpha
from plotpy.coords import canvas_to_axes, pixelround
from plotpy.interfaces import (
    IBaseImageItem,
    IBasePlotItem,
    IColormapImageItemType,
    ICSImageItemType,
    IExportROIImageItemType,
    IHistDataSource,
    IImageItemType,
    ISerializableType,
    ITrackableItemType,
    IVoiImageItemType,
)
from plotpy.items.image.base import RawImageItem
from plotpy.items.image.filter import XYImageFilterItem, to_bins
from plotpy.styles.image import ImageParam, RGBImageParam, XYImageParam

if TYPE_CHECKING:
    import guidata.io
    import qwt.scale_map
    from qtpy.QtCore import QPointF, QRectF
    from qtpy.QtGui import QColor, QPainter

    from plotpy.interfaces import IItemType
    from plotpy.widgets.colormap.widget import EditableColormap

try:
    from plotpy._scaler import INTERP_NEAREST, _scale_rect, _scale_xy
except ImportError:
    print(
        ("Module 'plotpy.items.image': missing C extension"),
        file=sys.stderr,
    )
    print(
        ("try running :python setup.py build_ext --inplace -c mingw32"),
        file=sys.stderr,
    )
    raise


# ==============================================================================
# Image item
# ==============================================================================
[docs] class ImageItem(RawImageItem): """Image item Args: data: 2D NumPy array param: Image parameters """ _can_move = True _can_resize = True __implements__ = ( IBasePlotItem, IBaseImageItem, IHistDataSource, IVoiImageItemType, IExportROIImageItemType, ) def __init__( self, data: np.ndarray | None = None, param: ImageParam | None = None ) -> None: self.xmin = None self.xmax = None self.ymin = None self.ymax = None super().__init__(data=data, param=param) # ---- BaseImageItem API ---------------------------------------------------
[docs] def get_default_param(self) -> ImageParam: """Return instance of the default image param DataSet""" return ImageParam(_("Image"))
# ---- Serialization methods ----------------------------------------------- def __reduce__(self) -> tuple: """Reduce object to be pickled""" fname = self.get_filename() if fname is None: fn_or_data = self.data else: fn_or_data = fname (xmin, xmax), (ymin, ymax) = self.get_xdata(), self.get_ydata() state = ( self.param, self.get_lut_range(), fn_or_data, self.z(), xmin, xmax, ymin, ymax, ) res = (self.__class__, (), state) return res def __setstate__(self, state: tuple) -> None: """Set object state from pickled state""" param, lut_range, fn_or_data, z, xmin, xmax, ymin, ymax = state self.set_xdata(xmin, xmax) self.set_ydata(ymin, ymax) self.param = param if isinstance(fn_or_data, str): self.set_filename(fn_or_data) self.load_data() elif fn_or_data is not None: # should happen only with previous API self.set_data(fn_or_data) self.set_lut_range(lut_range) self.setZ(z) self.param.update_item(self)
[docs] def serialize( self, writer: guidata.io.HDF5Writer | guidata.io.INIWriter | guidata.io.JSONWriter, ) -> None: """Serialize object to HDF5 writer Args: writer: HDF5, INI or JSON writer """ super().serialize(writer) (xmin, xmax), (ymin, ymax) = self.get_xdata(), self.get_ydata() writer.write(xmin, group_name="xmin") writer.write(xmax, group_name="xmax") writer.write(ymin, group_name="ymin") writer.write(ymax, group_name="ymax")
[docs] def deserialize( self, reader: guidata.io.HDF5Reader | guidata.io.INIReader | guidata.io.JSONReader, ) -> None: """Deserialize object from HDF5 reader Args: reader: HDF5, INI or JSON reader """ super().deserialize(reader) for attr in ("xmin", "xmax", "ymin", "ymax"): # Note: do not be tempted to write the symetric code in `serialize` # because calling `get_xdata` and `get_ydata` is necessary setattr(self, attr, reader.read(attr, func=reader.read_float))
# ---- Public API ----------------------------------------------------------
[docs] def get_xdata(self, aligned=True) -> tuple[float, float]: """Get X data range Args: aligned: True if aligned (Default value = True) Returns: (xmin, xmax) tuple """ xmin, xmax = self.xmin, self.xmax if xmin is None: xmin = 0.0 if xmax is None: xmax = self.data.shape[1] if aligned: dx = 0.5 * (xmax - xmin) / self.data.shape[1] xmin -= dx xmax -= dx return xmin, xmax
[docs] def get_ydata(self, aligned=True) -> tuple[float, float]: """Get Y data range Args: aligned: True if aligned (Default value = True) Returns: (ymin, ymax) tuple """ ymin, ymax = self.ymin, self.ymax if ymin is None: ymin = 0.0 if ymax is None: ymax = self.data.shape[0] if aligned: dy = 0.5 * (ymax - ymin) / self.data.shape[0] ymin -= dy ymax -= dy return ymin, ymax
[docs] def set_xdata(self, xmin: float | None = None, xmax: float | None = None) -> None: """Set X data range Args: xmin: Minimum X value xmax: Maximum X value """ self.xmin, self.xmax = xmin, xmax
[docs] def set_ydata(self, ymin: float | None = None, ymax: float | None = None) -> None: """Set Y data range Args: ymin: Minimum Y value ymax: Maximum Y value """ self.ymin, self.ymax = ymin, ymax
[docs] def update_bounds(self) -> None: """Update image bounds to fit image shape""" if self.data is None: return (xmin, xmax), (ymin, ymax) = self.get_xdata(), self.get_ydata() self.bounds = QC.QRectF(QC.QPointF(xmin, ymin), QC.QPointF(xmax, ymax))
# ---- BaseImageItem API ---------------------------------------------------
[docs] def get_pixel_coordinates(self, xplot: float, yplot: float) -> tuple[float, float]: """Get pixel coordinates from plot coordinates Args: xplot: X plot coordinate yplot: Y plot coordinate Returns: Pixel coordinates """ (xmin, xmax), (ymin, ymax) = self.get_xdata(), self.get_ydata() xpix = self.data.shape[1] * (xplot - xmin) / float(xmax - xmin) ypix = self.data.shape[0] * (yplot - ymin) / float(ymax - ymin) return xpix, ypix
[docs] def get_plot_coordinates(self, xpixel: float, ypixel: float) -> tuple[float, float]: """Get plot coordinates from pixel coordinates Args: xpixel: X pixel coordinate ypixel: Y pixel coordinate Returns: Plot coordinates """ (xmin, xmax), (ymin, ymax) = self.get_xdata(), self.get_ydata() xplot = xmin + (xmax - xmin) * xpixel / float(self.data.shape[1]) yplot = ymin + (ymax - ymin) * ypixel / float(self.data.shape[0]) return xplot, yplot
[docs] def get_x_values(self, i0: int, i1: int) -> np.ndarray: """Get X values from pixel indexes Args: i0: First index i1: Second index Returns: X values corresponding to the given pixel indexes """ xmin, xmax = self.get_xdata() def xfunc(index): return xmin + (xmax - xmin) * index / float(self.data.shape[1]) return np.linspace(xfunc(i0), xfunc(i1), i1 - i0, endpoint=False)
[docs] def get_y_values(self, j0: int, j1: int) -> np.ndarray: """Get Y values from pixel indexes Args: j0: First index j1: Second index Returns: Y values corresponding to the given pixel indexes """ ymin, ymax = self.get_ydata() def yfunc(index): return ymin + (ymax - ymin) * index / float(self.data.shape[0]) return np.linspace(yfunc(j0), yfunc(j1), j1 - j0, endpoint=False)
[docs] def get_closest_coordinates(self, x: float, y: float) -> tuple[float, float]: """ Get the closest coordinates to the given point Args: x: X coordinate y: Y coordinate Returns: tuple[float, float]: Closest coordinates """ xmin, xmax = self.get_xdata(aligned=False) ymin, ymax = self.get_ydata(aligned=False) i, j = self.get_closest_indexes(x, y) xpix = np.linspace(xmin, xmax, self.data.shape[1] + 1) ypix = np.linspace(ymin, ymax, self.data.shape[0] + 1) return xpix[i], ypix[j]
def _rescale_src_rect( self, src_rect: tuple[float, float, float, float] ) -> tuple[float, float, float, float]: """Rescale source rectangle Args: src_rect: Source rectangle Returns: Rescaled source rectangle """ sxl, syt, sxr, syb = src_rect xl, yt, xr, yb = self.boundingRect().getCoords() H, W = self.data.shape[:2] if xr - xl != 0: x0 = W * (sxl - xl) / (xr - xl) x1 = W * (sxr - xl) / (xr - xl) else: x0 = x1 = 0 if yb - yt != 0: y0 = H * (syt - yt) / (yb - yt) y1 = H * (syb - yt) / (yb - yt) else: y0 = y1 = 0 return x0, y0, x1, y1
[docs] def draw_image( self, painter: QPainter, canvasRect: QRectF, src_rect: tuple[float, float, float, float], dst_rect: tuple[float, float, float, float], xMap: qwt.scale_map.QwtScaleMap, yMap: qwt.scale_map.QwtScaleMap, ) -> None: """Draw image Args: painter: Painter canvasRect: Canvas rectangle src_rect: Source rectangle dst_rect: Destination rectangle xMap: X axis scale map yMap: Y axis scale map """ if self.data is None: return if self.warn_if_non_linear_scale(painter, canvasRect): return src2 = self._rescale_src_rect(src_rect) dst_rect = tuple([int(i) for i in dst_rect]) # Not the most efficient way to do it, but it works... # -------------------------------------------------------------------------- if self.get_zaxis_log_state(): data = self._log_data else: data = self.data # -------------------------------------------------------------------------- try: dest = _scale_rect( data, src2, self._offscreen, dst_rect, self.lut, self.interpolate ) except ValueError: # This exception is raised when zooming unreasonably inside a pixel return qrect = QC.QRectF(QC.QPointF(dest[0], dest[1]), QC.QPointF(dest[2], dest[3])) painter.drawImage(qrect, self._image, qrect)
[docs] def export_roi( self, src_rect: tuple[float, float, float, float], dst_rect: tuple[float, float, float, float], dst_image: np.ndarray, apply_lut: bool = False, apply_interpolation: bool = False, original_resolution: bool = False, force_interp_mode: str | None = None, force_interp_size: int | None = None, ) -> None: """ Export a rectangular area of the image to another image Args: src_rect: Source rectangle dst_rect: Destination rectangle dst_image: Destination image apply_lut: Apply lut (Default value = False) apply_interpolation: Apply interpolation (Default value = False) original_resolution: Original resolution (Default value = False) force_interp_mode: Force interpolation mode (Default value = None) force_interp_size: Force interpolation size (Default value = None) """ if apply_lut: a, b, _bg, _cmap = self.lut else: a, b = 1.0, 0.0 interp = self.interpolate if apply_interpolation else (INTERP_NEAREST,) rescaled_src = self._rescale_src_rect(src_rect) _scale_rect( self.data, rescaled_src, dst_image, dst_rect, (a, b, None), interp, )
[docs] def move_local_point_to(self, handle: int, pos: QPointF, ctrl: bool = None) -> None: """Move a handle as returned by hit_test to the new position Args: handle: Handle pos: Position ctrl: True if <Ctrl> button is being pressed, False otherwise """ nx, ny = canvas_to_axes(self, pos) xmin, xmax = self.get_xdata() ymin, ymax = self.get_ydata() handle_x = [xmin, xmax, xmax, xmin][handle] handle_y = [ymin, ymin, ymax, ymax][handle] sign_xmin = [1, -1, -1, 1][handle] sign_xmax = [-1, 1, 1, -1][handle] sign_ymin = [1, 1, -1, -1][handle] sign_ymax = [-1, -1, 1, 1][handle] delta_x = nx - handle_x delta_y = ny - handle_y self.set_xdata(xmin + delta_x * sign_xmin, xmax + delta_x * sign_xmax) self.set_ydata(ymin + delta_y * sign_ymin, ymax + delta_y * sign_ymax) self.update_bounds() self.update_border()
[docs] def move_local_shape(self, old_pos: QPointF, new_pos: QPointF) -> None: """Translate the shape such that old_pos becomes new_pos in canvas coordinates Args: old_pos: Old position new_pos: New position """ nx, ny = canvas_to_axes(self, new_pos) ox, oy = canvas_to_axes(self, old_pos) xmin, xmax = self.get_xdata() ymin, ymax = self.get_ydata() self.set_xdata(xmin + nx - ox, xmax + nx - ox) self.set_ydata(ymin + ny - oy, ymax + ny - oy) self.update_bounds() self.update_border() if self.plot(): self.plot().SIG_ITEM_MOVED.emit(self, ox, oy, nx, ny)
[docs] def move_with_selection(self, delta_x: float, delta_y: float) -> None: """Translate the item together with other selected items Args: delta_x: Translation in plot coordinates along x-axis delta_y: Translation in plot coordinates along y-axis """ xmin, xmax = self.get_xdata() ymin, ymax = self.get_ydata() self.set_xdata(xmin + delta_x, xmax + delta_x) self.set_ydata(ymin + delta_x, ymax + delta_y) self.update_bounds() self.update_border()
assert_interfaces_valid(ImageItem)
[docs] class XYImageItem(RawImageItem): """XY image item (non-linear axes) Args: x: 1D NumPy array of pixel center coordinates, must be increasing y: 1D NumPy array of pixel center coordinates, must be increasing data: 2D NumPy array param: image parameters Note: Internally, `self.x` and `self.y` store **bin edges** (boundaries between pixels), not pixel centers. If input arrays have length nj and ni (matching data dimensions), they are converted to bin edges of length nj+1 and ni+1 using `to_bins()`. Methods that need pixel center coordinates must compute them from the stored bin edges. """ __implements__ = ( IBasePlotItem, IBaseImageItem, IHistDataSource, IVoiImageItemType, IExportROIImageItemType, ISerializableType, ) def __init__( self, x: np.ndarray | None = None, y: np.ndarray | None = None, data: np.ndarray | None = None, param: XYImageParam | None = None, ) -> None: # if x and y are not increasing arrays, sort them and data accordingly if x is not None and not np.all(np.diff(x) >= 0): x_idx = np.argsort(x) x = x[x_idx] data = data[:, x_idx] if y is not None and not np.all(np.diff(y) >= 0): y_idx = np.argsort(y) y = y[y_idx] data = data[y_idx, :] super().__init__(data, param) self.x = None self.y = None if x is not None and y is not None: self.set_xy(x, y) # ---- Public API ---------------------------------------------------------- # ---- BaseImageItem API ---------------------------------------------------
[docs] def get_default_param(self) -> XYImageParam: """Return instance of the default image param DataSet""" return XYImageParam(_("Image"))
# ---- Pickle methods ------------------------------------------------------ def __reduce__(self) -> tuple: """Reduce object to be pickled""" fname = self.get_filename() if fname is None: fn_or_data = self.data else: fn_or_data = fname state = (self.param, self.get_lut_range(), self.x, self.y, fn_or_data, self.z()) res = (self.__class__, (), state) return res def __setstate__(self, state: tuple) -> None: """Set object state from pickled state""" param, lut_range, x, y, fn_or_data, z = state self.param = param if isinstance(fn_or_data, str): self.set_filename(fn_or_data) self.load_data(lut_range) elif fn_or_data is not None: # should happen only with previous API self.set_data(fn_or_data, lut_range=lut_range) self.set_xy(x, y) self.setZ(z) self.param.update_item(self)
[docs] def serialize( self, writer: guidata.io.HDF5Writer | guidata.io.INIWriter | guidata.io.JSONWriter, ) -> None: """Serialize object to HDF5 writer Args: writer: HDF5, INI or JSON writer """ super().serialize(writer) writer.write(self.x, group_name="Xdata") writer.write(self.y, group_name="Ydata")
[docs] def deserialize( self, reader: guidata.io.HDF5Reader | guidata.io.INIReader | guidata.io.JSONReader, ) -> None: """Deserialize object from HDF5 reader Args: reader: HDF5, INI or JSON reader """ super().deserialize(reader) x = reader.read(group_name="Xdata", func=reader.read_array) y = reader.read(group_name="Ydata", func=reader.read_array) self.set_xy(x, y)
# ---- Public API ----------------------------------------------------------
[docs] def set_xy(self, x: np.ndarray | list[float], y: np.ndarray | list[float]) -> None: """Set X and Y data Args: x: 1D NumPy array of pixel center coordinates, must be increasing y: 1D NumPy array of pixel center coordinates, must be increasing Raises: ValueError: If X or Y are not increasing IndexError: If X or Y are not of the right length Note: Input arrays represent pixel **center** coordinates. Internally, they are stored as bin **edges** in `self.x` and `self.y`: - If input has length nj (or ni), it's converted to edges via `to_bins()` - If input already has length nj+1 (or ni+1), it's used directly as edges - The stored edges have length nj+1 and ni+1 (one more than data dimensions) """ ni, nj = self.data.shape x = np.array(x, float) y = np.array(y, float) if not np.all(np.diff(x) >= 0): raise ValueError("x must be an increasing 1D array") if not np.all(np.diff(y) >= 0): raise ValueError("y must be an increasing 1D array") if x.shape[0] == nj: self.x = to_bins(x) elif x.shape[0] == nj + 1: self.x = x else: raise IndexError(f"x must be a 1D array of length {nj:d} or {nj + 1:d}") if y.shape[0] == ni: self.y = to_bins(y) elif y.shape[0] == ni + 1: self.y = y else: raise IndexError(f"y must be a 1D array of length {ni:d} or {ni + 1:d}") self.bounds = QC.QRectF( QC.QPointF(self.x[0], self.y[0]), QC.QPointF(self.x[-1], self.y[-1]) ) self.update_border()
# --- BaseImageItem API ----------------------------------------------------
[docs] def get_filter(self, filterobj, filterparam): """Provides a filter object over this image's content""" return XYImageFilterItem(self, filterobj, filterparam)
[docs] def draw_image( self, painter: QPainter, canvasRect: QRectF, src_rect: tuple[float, float, float, float], dst_rect: tuple[float, float, float, float], xMap: qwt.scale_map.QwtScaleMap, yMap: qwt.scale_map.QwtScaleMap, ) -> None: """Draw image Args: painter: Painter canvasRect: Canvas rectangle src_rect: Source rectangle dst_rect: Destination rectangle xMap: X axis scale map yMap: Y axis scale map """ if self.warn_if_non_linear_scale(painter, canvasRect): return xytr = self.x, self.y, src_rect dst_rect = tuple([int(i) for i in dst_rect]) if self.get_zaxis_log_state(): data = self._log_data else: data = self.data dest = _scale_xy( data, xytr, self._offscreen, dst_rect, self.lut, self.interpolate ) qrect = QC.QRectF(QC.QPointF(dest[0], dest[1]), QC.QPointF(dest[2], dest[3])) painter.drawImage(qrect, self._image, qrect)
[docs] def export_roi( self, src_rect: tuple[float, float, float, float], dst_rect: tuple[float, float, float, float], dst_image: np.ndarray, apply_lut: bool = False, apply_interpolation: bool = False, original_resolution: bool = False, force_interp_mode: str | None = None, force_interp_size: int | None = None, ) -> None: """Export a rectangular area of the image to another image Args: src_rect: Source rectangle in plot coordinates dst_rect: Destination rectangle in pixel coordinates dst_image: Destination image array apply_lut: Apply LUT (Default value = False) apply_interpolation: Apply interpolation (Default value = False) original_resolution: Original resolution (Default value = False) force_interp_mode: Force interpolation mode (Default value = None) force_interp_size: Force interpolation size (Default value = None) """ if apply_lut: lut = self.lut else: a, b = 1.0, 0.0 lut = (a, b, None, np.zeros((1,), np.uint32)) interp = self.interpolate if apply_interpolation else (INTERP_NEAREST,) xytr = self.x, self.y, src_rect _scale_xy(self.data, xytr, dst_image, dst_rect, lut, interp)
[docs] def get_pixel_coordinates(self, xplot: float, yplot: float) -> tuple[float, float]: """Get pixel coordinates from plot coordinates Args: xplot: X plot coordinate yplot: Y plot coordinate Returns: Pixel coordinates (integer pixel indices) """ # self.x and self.y are bin edges. searchsorted finds the right edge index. # To get the pixel index, we need the left edge index, which is right_edge - 1 # But clamp to valid range [0, n-1] where n is number of pixels i = self.x.searchsorted(xplot) j = self.y.searchsorted(yplot) # searchsorted returns the insertion point (right edge index) # Pixel index is insertion_point - 1, but clamped to [0, data.shape-1] i = max(0, min(i - 1, self.data.shape[1] - 1)) j = max(0, min(j - 1, self.data.shape[0] - 1)) return i, j
[docs] def get_plot_coordinates(self, xpixel: float, ypixel: float) -> tuple[float, float]: """Get plot coordinates from pixel coordinates Args: xpixel: X pixel coordinate ypixel: Y pixel coordinate Returns: Plot coordinates (pixel center coordinates) """ # self.x and self.y store bin edges, compute centers i = int(pixelround(xpixel)) j = int(pixelround(ypixel)) # Protect against out-of-bounds access i = max(0, min(i, len(self.x) - 2)) j = max(0, min(j, len(self.y) - 2)) x_center = (self.x[i] + self.x[i + 1]) / 2.0 y_center = (self.y[j] + self.y[j + 1]) / 2.0 return x_center, y_center
[docs] def get_x_values(self, i0: int, i1: int) -> np.ndarray: """Get X values from pixel indexes Args: i0: First index i1: Second index Returns: X values corresponding to the given pixel indexes (pixel centers) """ # self.x stores bin edges (length nj+1), but we need to return pixel centers # Compute centers from edges: center[i] = (edge[i] + edge[i+1]) / 2 # (Fixes issue #49 - Using cross-section tools on `XYImageItem` images alters # the X/Y coordinate arrays) return (self.x[i0:i1] + self.x[i0 + 1 : i1 + 1]) / 2.0
[docs] def get_y_values(self, j0: int, j1: int) -> np.ndarray: """Get Y values from pixel indexes Args: j0: First index j1: Second index Returns: Y values corresponding to the given pixel indexes (pixel centers) """ # self.y stores bin edges (length ni+1), but we need to return pixel centers # Compute centers from edges: center[j] = (edge[j] + edge[j+1]) / 2 return (self.y[j0:j1] + self.y[j0 + 1 : j1 + 1]) / 2.0
[docs] def get_closest_coordinates(self, x: float, y: float) -> tuple[float, float]: """ Get the closest coordinates to the given point Args: x: X coordinate y: Y coordinate Returns: tuple[float, float]: Closest pixel center coordinates """ i, j = self.get_closest_indexes(x, y) # self.x and self.y store bin edges, compute centers # Protect against out-of-bounds access i = max(0, min(i, len(self.x) - 2)) j = max(0, min(j, len(self.y) - 2)) x_center = (self.x[i] + self.x[i + 1]) / 2.0 y_center = (self.y[j] + self.y[j + 1]) / 2.0 return x_center, y_center
# ---- IBasePlotItem API ---------------------------------------------------
[docs] def types(self) -> tuple[type[IItemType], ...]: """Returns a group or category for this item. This should be a tuple of class objects inheriting from IItemType Returns: tuple: Tuple of class objects inheriting from IItemType """ return ( IImageItemType, IVoiImageItemType, IColormapImageItemType, ITrackableItemType, ISerializableType, ICSImageItemType, IExportROIImageItemType, )
# ---- IBaseImageItem API --------------------------------------------------
[docs] def can_sethistogram(self) -> bool: """ Returns True if this item can be associated with a levels histogram Returns: bool: True if item can be associated with a levels histogram, False otherwise """ return True
assert_interfaces_valid(XYImageItem) # ============================================================================== # RGB Image with alpha channel # ==============================================================================
[docs] class RGBImageItem(ImageItem): """RGB image item Args: data: 3D NumPy array (shape: NxMx[34] -- 3: RGB, 4: RGBA) param: image parameters """ __implements__ = (IBasePlotItem, IBaseImageItem, ISerializableType) def __init__( self, data: np.ndarray | None = None, param: RGBImageParam | None = None ) -> None: self.orig_data = None super().__init__(data, param) self.lut = None # ---- BaseImageItem API ---------------------------------------------------
[docs] def get_default_param(self) -> RGBImageParam: """Return instance of the default image param DataSet""" return RGBImageParam(_("Image"))
# ---- Public API ----------------------------------------------------------
[docs] def recompute_alpha_channel(self) -> None: """Recompute alpha channel""" data = self.orig_data if self.orig_data is None: return H, W, NC = data.shape R = data[..., 0].astype(np.uint32) G = data[..., 1].astype(np.uint32) B = data[..., 2].astype(np.uint32) use_alpha = self.param.alpha_function != LUTAlpha.NONE.value alpha = self.param.alpha if NC > 3 and use_alpha: A = data[..., 3].astype(np.uint32) else: A = np.zeros((H, W), np.uint32) A[:, :] = int(255 * alpha) self.data[:, :] = (A << 24) + (R << 16) + (G << 8) + B
# --- BaseImageItem API ---------------------------------------------------- # Override lut/bg handling
[docs] def set_lut_range(self, lut_range: tuple[float, float]) -> None: """ Set the current active lut range .. warning:: This method is not implemented for this item type. """ pass
[docs] def set_background_color(self, qcolor: QColor | str) -> None: """Set background color Args: qcolor: Background color .. warning:: This method is not implemented for this item type. """ self.lut = None
[docs] def set_color_map( self, name_or_table: str | EditableColormap, invert: bool | None = None ) -> None: """Set colormap Args: name_or_table: Colormap name or colormap invert: True to invert colormap, False otherwise (Default value = None, i.e. do not change the default behavior) """ self.lut = None
# ---- RawImageItem API ----------------------------------------------------
[docs] def load_data(self) -> None: """Load data from item filename attribute""" data = io.imread(self.get_filename(), to_grayscale=False) self.set_data(data)
[docs] def set_data(self, data: np.ndarray) -> None: """Set image data Args: data: 2D NumPy array """ H, W, NC = data.shape self.orig_data = data self.data = np.empty((H, W), np.uint32) self.recompute_alpha_channel() self.update_bounds() self.update_border() self.lut = None
# ---- IBasePlotItem API ---------------------------------------------------
[docs] def types(self) -> tuple[type[IItemType], ...]: """Returns a group or category for this item. This should be a tuple of class objects inheriting from IItemType Returns: tuple: Tuple of class objects inheriting from IItemType """ return (IImageItemType, ITrackableItemType, ISerializableType)
# ---- IBaseImageItem API --------------------------------------------------
[docs] def can_sethistogram(self) -> bool: """ Returns True if this item can be associated with a levels histogram Returns: bool: True if item can be associated with a levels histogram, False otherwise """ return False
assert_interfaces_valid(RGBImageItem)