diff --git a/doc/source/conf.py b/doc/source/conf.py index f4153e7357..ae0e6ba790 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -35,14 +35,14 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode", - "sphinx.ext.napoleon", + "sphinx.ext.napoleon", # has to be loaded before sphinx_autodoc_typehints + "sphinx_autodoc_typehints", "sphinx.ext.intersphinx", "sphinx_qt_documentation", "sphinx_design", "sphinx_favicon", "sphinxext.rediraffe", "sphinxcontrib.images", - "sphinx_autodoc_typehints" ] # Add any paths that contain templates here, relative to this directory. @@ -67,6 +67,10 @@ napoleon_use_admonition_for_notes = True napoleon_use_admonition_for_references = True napoleon_use_rtype = True +napoleon_use_param = False +napoleon_use_keyword = False +napoleon_attr_annotations = True +napoleon_use_ivar = True napoleon_custom_sections = [("Signals", "params_style")] napoleon_preprocess_types = True napoleon_type_aliases = { @@ -77,6 +81,12 @@ # 'ColorMapSpecifier': ':class:`str`, (:class:`str`, :class:`str`), or :class:`~pyqtgraph.ColorMap`', } + +# makes things far more legible +python_use_unqualified_type_names = True +python_display_short_literal_types = True + + # The encoding of source files. #source_encoding = 'utf-8-sig' @@ -135,13 +145,23 @@ # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] -autodoc_inherit_docstrings = False +# Automatically extract typehints when specified and place them in +# descriptions of the relevant function/method. +autodoc_typehints = "description" +autodoc_typehints_format = 'short' +autodoc_typehints_description_target = 'documented_params' +autodoc_typehints_defaults = 'braces' +autodoc_typehints_use_rtype = True + + autodoc_mock_imports = [ "scipy", "h5py", "matplotlib", ] +# autodoc_type_aliases = {} + # -- Options for HTML output --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 9bbb6317ae..441c04903b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,3 +50,19 @@ ignore_errors = true [[tool.mypy.overrides]] module = "pyqtgraph.flowchart.*" # the list will increase ignore_errors = true # will be changed to `false` when it's ready + +[tool.numpydoc_validation] +checks = [ + "all", # report on all checks, except the below + "EX01", + "SA01", + "GL08", # object does not have a docstring + "ES01", # no extended summary found +] +# remember to use single quotes for regex in TOML +exclude = [ # don't report on objects that match any of these regex +] + +override_GL06 = [ + 'Signals' # allow sections to start with "Signals" +] diff --git a/pyqtgraph/graphicsItems/ImageItem.py b/pyqtgraph/graphicsItems/ImageItem.py index 62a3cfa1ed..7f9599a837 100644 --- a/pyqtgraph/graphicsItems/ImageItem.py +++ b/pyqtgraph/graphicsItems/ImageItem.py @@ -3,7 +3,7 @@ import warnings from collections.abc import Callable -import numpy +import numpy as np import numpy.typing as npt from .. import colormap @@ -22,7 +22,8 @@ class ImageItem(GraphicsObject): - """Graphics object used to display image data. + """ + Graphics object used to display image data. ImageItem can render images with 1, 3 or 4 channels, use lookup tables to apply false colors to images, and users can either set levels limits, or rely on @@ -47,19 +48,23 @@ class ImageItem(GraphicsObject): sigImageChanged = QtCore.Signal() sigRemoveRequested = QtCore.Signal(object) - def __init__(self, image=None, **kargs): + def __init__(self, image: np.ndarray | None=None, **kargs): """ + Initializer for :class:`~pyqtgraph.ImageItem`. + Parameters ---------- - image: np.ndarray, optional - Image data, default is None - **kargs: - Arguments directed to :func:`setImage` and :func:`setOpts` + image : np.ndarray or None, default None + Image data. + **kargs : + Arguments directed to :func:`setImage` and :func:`setOpts`. + See Also -------- - setImage : for descriptions of available keyword arguments - setOpts : for information on supported formats - + setImage : + For descriptions of available keyword arguments. + setOpts : + For information on supported formats. """ super().__init__() self.menu = None @@ -96,41 +101,44 @@ def __init__(self, image=None, **kargs): def setCompositionMode(self, mode: QtGui.QPainter.CompositionMode): """ - Change the composition mode of the item. This is useful when overlaying - multiple items. + Change the composition mode of the item, useful when overlaying multiple items. Parameters ---------- - mode : QPainter.CompositionMode + mode : :class:`QPainter.CompositionMode ` Composition of the item, often used when overlaying items. Common options include: * `QPainter.CompositionMode.CompositionMode_SourceOver` Image replaces the background if it is opaque. Otherwise, it uses the - alpha channel to blend the image with the background, default + alpha channel to blend the image with the background, default. * `QPainter.CompositionMode.CompositionMode_Overlay` Image color is mixed with the background color to reflect the lightness or darkness of - the background + the background. * `QPainter.CompositionMode.CompositionMode_Plus` Both the alpha and color of the image and background pixels are added together. * `QPainter.CompositionMode.CompositionMode_Plus` The output is the image color multiplied by the background. See :class:`QPainter.CompositionMode ` in the Qt - documentation for more options and details + documentation for more options and details. See Also -------- - :class:`QPainter.CompositionMode ` : Details all the - possible composition mode options accepted. + :class:`QPainter.CompositionMode ` : + Details all the possible composition mode options accepted. """ self.paintMode = mode self.update() def setBorder(self, b): """ - Defines the border drawn around the image. Accepts all arguments supported by - :func:`~pyqtgraph.mkPen`. + Define the color of the border drawn around the image. + + Parameters + ---------- + b : color_like + Accepts all arguments supported by :func:`~pyqtgraph.mkPen`. """ self.border = fn.mkPen(b) self.update() @@ -159,27 +167,34 @@ def boundingRect(self) -> QtCore.QRectF: def setLevels(self, levels: npt.ArrayLike | None, update: bool=True): """ - Sets image scaling levels. Calling this method, even with ``levels=None`` will - disable auto leveling (set ``autoLevels=False``). + Set image scaling levels. + + Calling this method, even with ``levels=None`` will disable auto leveling + which is equivalent to `:func:`setImage` with ``autoLevels=False``. Parameters ---------- levels : array_like or None - - ``[blackLevel, whiteLevel]`` + Sets the numerical values that correspond to the limits of the color range. + + * ``[blackLevel, whiteLevel]`` sets black and white levels for monochrome data and can be used with a lookup table. - - ``[[minR, maxR], [minG, maxG], [minB, maxB]]`` - sets individual scaling for RGB values. Not compatible with lookup tables. - - ``None`` + * ``[[minR, maxR], [minG, maxG], [minB, maxB]]`` + sets individual scaling for RGB values. Not compatible with lookup + tables. + * ``None`` Disables the application of levels, but setting to ``None`` prevents the auto-levels mechanism from sampling the image. Not compatible with images that use floating point dtypes. - update : bool, optional - Controls if image immediately updates to reflect the new levels, default True + + update : bool, default True + Update the image immediately to reflect th new levels. See Also -------- - pyqtgraph.functions.makeARGB : for more details on how levels are applied. + pyqtgraph.functions.makeARGB : + For more details on how levels are applied. """ if self._xp is None: self.levels = levels @@ -191,28 +206,34 @@ def setLevels(self, levels: npt.ArrayLike | None, update: bool=True): if update: self.updateImage() - def getLevels(self) -> numpy.ndarray | None: + def getLevels(self) -> np.ndarray | None: """ - Returns the array representing the current level settings. See - :func:`setLevels`. When `autoLevels` is active, the format is + Return the array representing the current level settings. + + See :func:`setLevels`. When `autoLevels` is active, the format is ``[blackLevel, whiteLevel]``. + + Returns + ------- + np.ndarray or None + The value that the levels are set to. """ return self.levels def setColorMap(self, colorMap: colormap.ColorMap | str): """ - Sets a color map for false color display of a monochrome image. + Set a color map for false color display of a monochrome image. Parameters ---------- colorMap : :class:`~pyqtgraph.ColorMap` or `str` A string argument will be passed to - :func:`colormap.get() ` + :func:`colormap.get() `. Raises ------ TypeError - Raised when `colorMap` is not of type `str` or :class:`~pyqtgraph.ColorMap` + Raised when `colorMap` is not of type `str` or :class:`~pyqtgraph.ColorMap`. """ if isinstance(colorMap, colormap.ColorMap): self._colorMap = colorMap @@ -224,17 +245,18 @@ def setColorMap(self, colorMap: colormap.ColorMap | str): def getColorMap(self) -> colormap.ColorMap | None: """ + Retrive the :class:`~pyqtgraph.ColorMap` object currently used. + Returns ------- ColorMap or None - The assigned :class:`~pyqtgraph.ColorMap`, or `None` if not available + The assigned :class:`~pyqtgraph.ColorMap`, or `None` if not available. """ return self._colorMap def setLookupTable(self, lut: npt.ArrayLike | Callable, update: bool=True): - """Sets lookup table `lut` to use for false color display of a monochrome - image. See :func:`makeARGB ` for more - information on how this is used. + """ + Set lookup table `lut` to use for false color display of a monochrome image. Ordinarily, this table is supplied by a :class:`~pyqtgraph.HistogramLUTItem`, :class:`~pyqtgraph.GradientEditorItem` or :class:`~pyqtgraph.ColorBarItem`. @@ -244,12 +266,19 @@ def setLookupTable(self, lut: npt.ArrayLike | Callable, update: bool=True): lut : array_like or callable `lut` can be a callable that accepts the current image as an argument and returns the lookup table to use. - update : bool, optional - Update the intermediate image, by default True. + update : bool, default True + Update the intermediate image. See Also -------- :func:`pyqtgraph.functions.makeARGB` + See this function for more information on how this is used. + + Notes + ----- + For performance reasons, if not passing a callable, every effort should be made + to keep the number of entries to `<= 256`, and ideally should be of dtype + `uint8`. """ if lut is not self.lut: @@ -266,24 +295,25 @@ def _ensure_proper_substrate(data: Callable | npt.ArrayLike, substrate): cupy = getCupy() if substrate == cupy and not isinstance(data, cupy.ndarray): data = cupy.asarray(data) - elif substrate == numpy: + elif substrate == np: if cupy is not None and isinstance(data, cupy.ndarray): data = data.get() else: - data = numpy.asarray(data) + data = np.asarray(data) return data def setAutoDownsample(self, active: bool=True): - """Controls automatic downsampling for this ImageItem. + """ + Control automatic downsampling for this ImageItem. Parameters ---------- - active : bool, optional - If `active` is `True`, the image is automatically downsampled to match the - screen resolution. This improves performance for large images and reduces - aliasing. If `autoDownsample` is not specified, then ImageItem will + active : bool, default True + If `active` is ``True``, the image is automatically downsampled to match + the screen resolution. This improves performance for large images and + reduces aliasing. If `autoDownsample` is not specified, then ImageItem will choose whether to downsample the image based on its size. ``False`` - disables automatic downsampling. by default ``True`` + disables automatic downsampling. """ self.autoDownsample = active self._renderRequired = True @@ -291,25 +321,26 @@ def setAutoDownsample(self, active: bool=True): def setOpts(self, update: bool=True, **kargs): """ - Sets display and processing options for this ImageItem. + Set display and processing options for this ImageItem. + :func:`~pyqtgraph.ImageItem.__init__` and :func:`setImage` support all keyword arguments listed here. Parameters ---------- - autoDownsample : bool + autoDownsample : bool, default True See :func:`setAutoDownsample`. axisOrder : { 'col-major', 'row-major' } The shape of the array represents (width, height) of the image. ``'col-major'`` is the default, but ``'row-major'`` should be used whenever - possible for performance reasons + possible for performance reasons. border : bool Sets a pen to draw to draw an image border. See :func:`setBorder`. compositionMode : QPainter.CompositionMode - See :func:`setCompositionMode` + See :func:`setCompositionMode`. colorMap : :class:`~pyqtgraph.ColorMap` or `str` Sets a color map. A string will be passed to - :func:`colormap.get() ` + :func:`colormap.get() `. lut : array_like Sets a color lookup table to use when displaying the image. See :func:`setLookupTable`. @@ -324,16 +355,21 @@ def setOpts(self, update: bool=True, **kargs): Displays the current image within the specified rectangle in plot coordinates. If ``array_like``, should be of the of ``floats ('x','y','w','h')`` . See :func:`setRect`. - update : bool, optional + update : bool, default True Controls if image immediately updates to reflect the new options. See Also -------- :func:`setCompositionMode` + See for usage of he `compositionMode` keyword argument. :func:`setLevels` + See for usage of the `levels` keyword argument. :func:`setLookupTable` + See for usage of the `lut` keyword argument. :func:`setRect` + See for usage of the `rect` keyword argument. :func:`setImage` + See for more supported keyword arguments. """ if 'axisOrder' in kargs: val = kargs['axisOrder'] @@ -366,25 +402,29 @@ def setOpts(self, update: bool=True, **kargs): self.update() def setRect(self, *args): - """Sets translation and scaling of this ImageItem to display the current image - within the rectangle given as `rect` (:class:`QRect` or :class:`QRectF`), or - described by parameters `x, y, w, h`, defining starting position, width and - height. + """ + Set view rectangle for the :class:`~pyqtgraph.ImageItem` to occupy. + + In addition to accepting a :class:`QRectF`, you can pass the numerical values + represting the `x, y, w, h`, where `x, y` represent the x, y coordinates + of the top left corner, and `w` and `h` represent the width and height + respectively. Parameters ---------- *args : tuple Contain, :class:`QRectF`, :class:`QRect`, or arguments that can be used to - construct :class:`QRectF` + construct :class:`QRectF`. + + See Also + -------- + :class:`QRectF` : + See constructor methods for allowable `*args`. Notes ----- This method cannot be used before an image is assigned. See the :ref:`examples ` for how to manually set transformations. - - See Also - -------- - :class:`QRectF` : See constructor methods for allowable ``*args`` """ if not args: # reset scaling and rotation when called without argument @@ -404,7 +444,7 @@ def setRect(self, *args): def clear(self): """ - Clears the assigned image. + Clear the assigned image. """ self.image = None self.prepareGeometryChange() @@ -412,7 +452,7 @@ def clear(self): self.update() def _buildQImageBuffer(self, shape: tuple[int, int, int]): - self._displayBuffer = numpy.empty(shape[:2] + (4,), dtype=numpy.ubyte) + self._displayBuffer = np.empty(shape[:2] + (4,), dtype=np.ubyte) if self._xp == getCupy(): self._processingBuffer = self._xp.empty( shape[:2] + (4,), @@ -424,40 +464,47 @@ def _buildQImageBuffer(self, shape: tuple[int, int, int]): def setImage( self, - image: numpy.ndarray | None=None, + image: np.ndarray | None=None, autoLevels: bool | None=None, **kargs ): """ - Updates the image displayed by this ImageItem. + Update the image displayed by this ImageItem. All keywords supported by :func:`setOpts` are also allowed here. Parameters ---------- - image : np.ndarray, optional + image : np.ndarray or None, default None Image data given as NumPy array with an integer or floating point dtype of any bit depth. A 2-dimensional array describes single-valued (monochromatic) data. A 3-dimensional array is used to give individual color components. The third dimension must be of length 3 (RGB) or 4 (RGBA). ``np.nan`` values are treated as transparent pixels. - rect : QRectF, QRect or array_like, optional - If given, sets translation and scaling to display the image within the - specified rectangle. If ``array_like`` should be the form of floats - ``[x, y, w, h]`` See :func:`setRect` - autoLevels : bool, optional - If `True`, ImageItem will automatically select levels based on the maximum + autoLevels : bool or None, default None + If ``True``, ImageItem will automatically select levels based on the maximum and minimum values encountered in the data. For performance reasons, this search subsamples the images and may miss individual bright or dark points - in the data set. If `False`, the search will be omitted. + in the data set. If ``False``, the search will be omitted. If ``None``, and + the levels keyword argument is given, it will switch to ``False``, if the + `levels` argument is omitted, it will switch to ``True``. - The default is ``False`` if a `levels` keyword argument is given, and - ``True`` otherwise. + Other Parameters + ---------------- + rect : QRectF, QRect or array_like, optional + If given, sets translation and scaling to display the image within the + specified rectangle. If ``array_like`` should be the form of floats + ``[x, y, w, h]`` See :func:`setRect`. levelSamples : int, default 65536 When determining minimum and maximum values, ImageItem only inspects a subset of pixels no larger than this number. Setting this larger than the total number of pixels considers all values. - + + See Also + -------- + :func:`pyqtgraph.functions.makeARGB` + See this function for how image data is modified prior to rendering. + Notes ----- For backward compatibility, image data is assumed to be in column-major order @@ -470,14 +517,7 @@ def setImage( or the interpretation of the data can be changed locally through the `axisOrder` keyword or by changing the `imageAxisOrder` - :ref:`global configuration option ` - - For more information on how the image is processed before displaying, see - :func:`~pyqtgraph.makeARGB`. - - See Also - -------- - :func:`pyqtgraph.functions.makeARGB` + :ref:`global configuration option `. """ profile = debug.Profiler() @@ -488,7 +528,7 @@ def setImage( else: old_xp = self._xp cp = getCupy() - self._xp = cp.get_array_module(image) if cp else numpy + self._xp = cp.get_array_module(image) if cp else np gotNewData = True processingSubstrateChanged = old_xp != self._xp if processingSubstrateChanged: @@ -542,7 +582,13 @@ def setImage( self.setLevels((levels)) def _update_data_transforms(self, axisOrder: str='col-major'): - """Sets up the transforms needed to map between input array and display + """ + Set up the transforms needed to map between input array and display. + + Parameters + ---------- + axisOrder : { 'col-major', 'row-major' } + The axis order to update the data transformation to. """ self._dataTransform = QtGui.QTransform() self._inverseDataTransform = QtGui.QTransform() @@ -554,25 +600,37 @@ def _update_data_transforms(self, axisOrder: str='col-major'): def dataTransform(self): """ - Returns the transform that maps from this image's input array to its local - coordinate system. + Get the transform mapping image array to local coordinate system. This transform corrects for the transposition that occurs when image data is interpreted in row-major order. :meta private: + + Returns + ------- + :class:`QTransform` + The transform that is used for mapping. """ # Might eventually need to account for downsampling / clipping here # transforms are updated in setOpts call. return self._dataTransform def inverseDataTransform(self): - """Return the transform that maps from this image's local coordinate - system to its input array. - - See dataTransform() for more information. + """ + Get the transform mapping local coordinate system to image array. :meta private: + + Returns + ------- + :class:`QTransform` + The transform that is used for mapping. + + See Also + -------- + dataTransform + See dataTransform() for more information. """ # transforms are updated in setOpts call. return self._inverseDataTransform @@ -584,20 +642,22 @@ def mapFromData(self, obj): return self._dataTransform.map(obj) def quickMinMax(self, targetSize: int=1_000_000) -> tuple[float, float]: - """Estimates the min/max values of the image data by subsampling. Subsampling - is performed at regular strides chosen to evaluate a number of samples equal - to or less than `targetSize`. Returns the estimated min and max values of the - image data. + """ + Estimate the min and max values of the image data by subsampling. + + Subsampling is performed at regular strides chosen to evaluate a number of + samples equal to or less than `targetSize`. Returns the estimated min and max + values of the image data. Parameters ---------- - targetSize : int, optional - the number of pixels to downsample the image to, by default 1_000_000 + targetSize : int, default 1_000_000 + The number of pixels to downsample the image to. Returns ------- float, float - Estimated minimum and maximum values of the image data + Estimated minimum and maximum values of the image data. """ data = self.image @@ -677,10 +737,10 @@ def render(self): # a usage to work. Rather than fail outright, we delegate this # case to makeARGB(). warnings.warn( - "Using non-uint8 LUTs is an undocumented accidental feature and may " + ("Using non-uint8 LUTs is an undocumented accidental feature and may " "be removed at some point in the future. Please open an issue if you " "instead believe this to be worthy of protected inclusion in " - "pyqtgraph.", + "pyqtgraph."), DeprecationWarning, stacklevel=2 ) @@ -753,19 +813,23 @@ def paint(self, p, *args): p.drawRect(self.boundingRect()) def save(self, fileName: str | pathlib.Path, *args): - """Saves this image to file. Note that this saves the visible image - (after scale/color changes), not the original data. + """ + Save this image to file. + + Note that this saves the visible image, after scale/color changes, not the + original data. Parameters ---------- - fileName : str or pathlib.Path - path to save the image to - *args : tuple - arguments that are passed to :meth:`QImage.save ` + fileName : os.PathLike + File path to save the image data to. + *args : + Arguments that are passed to :meth:`QImage.save `. See Also -------- - :meth:`QImage.save ` : ``*args`` is relayed to this method + :meth:`QImage.save ` : + ``*args`` is relayed to this method. """ if self._renderRequired: @@ -775,34 +839,34 @@ def save(self, fileName: str | pathlib.Path, *args): def getHistogram( self, - bins ='auto', - step: str | numpy.generic ='auto', - perChannel=False, - targetImageSize=200, + bins: str | int='auto', + step: str | np.generic='auto', + perChannel: bool=False, + targetImageSize: int=200, **kwds - ) -> tuple[numpy.ndarray, numpy.ndarray] | tuple[None, None]: - """Generates arrays containing the histogram values similar to - :func:`numpy.histogram` + ) -> tuple[np.ndarray, np.ndarray] | tuple[None, None]: + """ + Generate arrays containing the histogram values. + + Similar to :func:`numpy.histogram` Parameters ---------- - bins : int or str, optional + bins : int or str, default 'auto' The `bins` argument and any extra keyword arguments are passed to :func:`numpy.histogram()`. If ``bins == 'auto'``, a bin number is - automatically chosen based on the image characteristics, by default - `'auto'` - step : str or int, optional + automatically chosen based on the image characteristics. + step : int or str, default 'auto' The `step` argument causes pixels to be skipped when computing the histogram to save time. If `step` is 'auto', then a step is chosen such that the analyzed data has dimensions approximating `targetImageSize` - for each axis, by default `'auto'` - perChannel : bool, optional + for each axis. + perChannel : bool, default False If ``True``, then a histogram is computed for each channel, - and the output is a list of the results, by default ``False`` - targetImageSize : int, optional + and the output is a list of the results. + targetImageSize : int, default 200 This parameter is used if ``step == 'auto'``, If so, the `step` size is - calculated by ``step = ceil(image.shape[0] / targetImageSize)``, by - default 200 + calculated by ``step = ceil(image.shape[0] / targetImageSize)``. Returns ------- @@ -811,11 +875,12 @@ def getHistogram( image. For an explanation of the return format, see :func:`numpy.histogram()`. - Returns ``(None, None)`` is there is no image, or image size is 0 + Returns ``(None, None)`` is there is no image, or image size is 0. See Also -------- - numpy.histogram : describes return format in greater detail + numpy.histogram : + Describes return format in greater detail. """ if 'targetHistogramSize' in kwds: @@ -881,20 +946,19 @@ def getHistogram( def setPxMode(self, b: bool): """ - Sets whether the item ignores transformations and draws directly to screen pixels. - If ``True``, the item will not inherit any scale or rotation transformations from its - parent items, but its position will be transformed as usual. + Set whether item ignores transformations and draws directly to screen pixels. Parameters ---------- b : bool - Enable pixel model, thus ignoring transformations + If ``True``, the item will not inherit any scale or rotation + transformations from its parent items, but its position will be transformed + as usual. See Also -------- - :class:`QGraphicsItem.GraphicsItemFlag` : Read the description of - `ItemIgnoresTransformations` for more information - + :class:`QGraphicsItem.GraphicsItemFlag` : + Read the description of `ItemIgnoresTransformations` for more information. """ self.setFlag( QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIgnoresTransformations, @@ -913,10 +977,12 @@ def getPixmap(self) -> QtGui.QPixmap | None: def pixelSize(self) -> tuple[float, float]: """ + Get the `x` and `y` size of each pixel in the view coordinate system. + Returns ------- - float, float - x, and y scene-size of a single pixel in the image + x, y: float, float + `x`, and `y` scene-size of a single pixel in the image. """ br = self.sceneBoundingRect() if self.image is None: