Source code for plotpy.items.histogram

# -*- coding: utf-8 -*-

# pylint: disable=C0103

"""
Histogram item
"""

from __future__ import annotations

import weakref
from typing import TYPE_CHECKING

import numpy as np
from guidata.dataset import update_dataset
from guidata.utils.misc import assert_interfaces_valid
from qwt import QwtPlotCurve

from plotpy.config import _
from plotpy.interfaces import IBasePlotItem, IHistDataSource
from plotpy.items.curve.base import CurveItem
from plotpy.styles.curve import CurveParam
from plotpy.styles.histogram import HistogramParam

if TYPE_CHECKING:
    from plotpy.items.image.base import BaseImageItem
    from plotpy.styles.base import ItemParameters


class HistDataSource:
    """An objects that provides an Histogram data source interface
    to a simple numpy array of data
    """

    __implements__ = (IHistDataSource,)

    def __init__(self, data: np.ndarray) -> None:
        self.data = data

    def get_histogram(
        self, nbins: int, drange: tuple[float, float] | None = None
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Return a tuple (hist, bins) where hist is a list of histogram values

        Args:
            nbins: number of bins
            drange: lower and upper range of the bins. If not provided, range is
             simply (data.min(), data.max()). Values outside the range are ignored.

        Returns:
            Tuple (hist, bins)
        """
        return np.histogram(self.data, bins=nbins, range=drange)


assert_interfaces_valid(HistDataSource)


[docs] class HistogramItem(CurveItem): """Histogram plot item Args: curveparam: Curve parameters histparam: Histogram parameters """ __implements__ = (IBasePlotItem,) _icon_name = "histogram.png" def __init__( self, curveparam: CurveParam | None = None, histparam: HistogramParam | None = None, keep_weakref: bool = False, ) -> None: self.hist_count = None self.hist_bins = None self.bins = None self.bin_range = None self.old_bins = None self.source: BaseImageItem | None = None self.logscale: bool | None = None self.old_logscale = None self.keep_weakref = keep_weakref if curveparam is None: curveparam = CurveParam(_("Curve"), icon="curve.png") curveparam.curvestyle = "Steps" if histparam is None: self.histparam = HistogramParam(title=_("Histogram"), icon="histogram.png") else: self.histparam = histparam CurveItem.__init__(self, curveparam) self.setCurveAttribute(QwtPlotCurve.Inverted)
[docs] def set_hist_source(self, src: BaseImageItem) -> None: """Set histogram source Args: src: Object with method `get_histogram`, e.g. objects derived from :py:class:`.ImageItem` """ if self.keep_weakref: self.source = weakref.ref(src) else: self.source = src self.update_histogram()
[docs] def get_hist_source(self) -> BaseImageItem | None: """Return histogram source Returns: object: Object with method `get_histogram`, e.g. objects derived from :py:class:`.ImageItem` """ if self.source is not None: if self.keep_weakref: return self.source() return self.source
[docs] def set_hist_data(self, data: np.ndarray) -> None: """Set histogram data Args: data: numpy array """ self.set_hist_source(HistDataSource(data))
[docs] def set_logscale(self, state: bool) -> None: """Sets whether we use a logarithm or linear scale for the histogram counts Args: state: True for logarithmic scale """ self.logscale = state self.update_histogram()
[docs] def get_logscale(self) -> bool | None: """Returns the status of the scale Returns: bool: True for logarithmic scale """ return self.logscale
[docs] def set_bins(self, n_bins: int) -> None: """Sets the number of bins Args: n_bins: number of bins """ self.bins = n_bins self.update_histogram()
[docs] def get_bins(self) -> int | None: """Returns the number of bins Returns: int: number of bins """ return self.bins
[docs] def set_bin_range(self, bin_range: tuple[float, float] | None) -> None: """Sets the range of the bins Args: bin_range: (min, max) or None for automatic range """ self.bin_range = bin_range self.update_histogram()
[docs] def get_bin_range(self) -> tuple[float, float] | None: """Returns the range of the bins Returns: tuple: (min, max) """ return self.bin_range
[docs] def compute_histogram(self) -> tuple[np.ndarray, np.ndarray]: """Compute histogram data Returns: tuple: (hist, bins) """ return self.get_hist_source().get_histogram(self.bins, self.bin_range)
[docs] def update_histogram(self) -> None: """Update histogram data""" if self.get_hist_source() is None: return hist, bin_edges = self.compute_histogram() # Duplicate the first `hist` value to get a step-like histogram: hist = np.concatenate(([hist[0]], hist)) if self.logscale: hist = np.log(hist + 1) self.set_data(bin_edges, hist) # Autoscale only if logscale/bins have changed if self.bins != self.old_bins or self.logscale != self.old_logscale: if self.plot(): self.plot().do_autoscale() self.old_bins = self.bins self.old_logscale = self.logscale plot = self.plot() if plot is not None: plot.do_autoscale(replot=True)
[docs] def update_params(self): """Update histogram parameters""" self.histparam.update_hist(self) CurveItem.update_params(self)
[docs] def get_item_parameters(self, itemparams: ItemParameters) -> None: """ Appends datasets to the list of DataSets describing the parameters used to customize apearance of this item Args: itemparams: Item parameters """ CurveItem.get_item_parameters(self, itemparams) itemparams.add("HistogramParam", self, self.histparam)
[docs] def set_item_parameters(self, itemparams): """ :param itemparams: """ update_dataset( self.histparam, itemparams.get("HistogramParam"), visible_only=True ) self.histparam.update_hist(self) CurveItem.set_item_parameters(self, itemparams)
assert_interfaces_valid(HistogramItem)