Skip to content

ndv.models #

Models for ndv.

Classes:

  • ArrayDisplayModel

    Model of how to display an array.

  • ChannelMode

    Channel display mode.

  • ClimPolicy

    ABC for contrast limit policies.

  • ClimsManual

    Manually specified contrast limits.

  • ClimsMinMax

    Autoscale contrast limits based on the minimum and maximum values in the data.

  • ClimsPercentile

    Autoscale contrast limits based on percentiles of the data.

  • ClimsStdDev

    Automatically set contrast limits based on standard deviations from the mean.

  • DataWrapper

    Interface for wrapping different array-like data types.

  • LUTModel

    Representation of how to display a channel of an array.

  • NDVModel

    Base evented model for NDV models.

ArrayDisplayModel #

Bases: NDVModel

Model of how to display an array.

An ArrayDisplayModel is used to specify how to display a multi-dimensional array. It specifies which axes are visible, how to reduce along axes that are not visible, how to display channels, and how to apply lookup tables to channels. It is typically paired with a ndv.DataWrapper in order to resolve axis keys and slice data.

Info

In the following types, Hashable is used to refer to a type that will typically be either an integer index or a string label for an axis.

Attributes:

  • visible_axes (tuple[Hashable, ...]) –

    Ordered list of axes to visualize, from slowest to fastest. e.g. ('Z', -2, -1)

  • current_index (Mapping[Hashable, int | slice]) –

    The currently displayed position/slice along each dimension. e.g. {0: 0, 'time': slice(10, 20)} Not all axes need be present, and axes not present are assumed to be slice(None), meaning it is up to the controller of this model to restrict indices to an efficient range for retrieval. If the number of non-singleton axes is greater than n_visible_axes, then reducers are used to reduce the data along the remaining axes.

  • reducers (Mapping[Hashable | None, ufunc]) –

    Function used to reduce data along axes remaining after slicing. Ideally, the ufunc should accept an axis argument. (TODO: what happens if not?)

  • default_reducer (ufunc) –

    Default reducer to use when no reducer is specified for an axis. By default, this is numpy.max.

  • channel_mode (ChannelMode) –

    How to display channel information:

    • GRAYSCALE: ignore channel axis, use default_lut
    • COMPOSITE: display all channels as a composite image, using luts
    • COLOR: display a single channel at a time, using luts
    • RGBA: display as an RGB image, using default_lut (except for cmap)

    If channel_mode is set to anything other than GRAYSCALE, then channel_axis must be set to a valid axis; if no channel_axis is set, at the time of display, the DataWrapper MAY guess the channel_axis, and set it on the model.

  • channel_axis (Hashable | None) –

    The dimension index or name of the channel dimension. The implication of setting channel_axis is that all elements along the channel dimension are shown, with different LUTs applied to each channel. If None, then a single lookup table is used for all channels (luts[None]). NOTE: it is an error for channel_axis to be in visible_axes (or ignore it?)

  • luts (Mapping[int | None, LUTModel]) –

    Instructions for how to display each channel of the array. Keys represent position along the dimension specified by channel_axis. Values are LUT objects that specify how to display the channel. The special key None is used to represent a fallback LUT for all channels, and is used when channel_axis is None. It should always be present

  • default_lut (LUTModel) –

    Default lookup table to use when no LUTModel is specified for a channel in luts.

Attributes:

n_visible_axes property #

n_visible_axes: Literal[2, 3]

Number of dims is derived from the length of visible_axes.

ChannelMode #

Bases: str, Enum

Channel display mode.

Attributes:

  • GRAYSCALE (str) –

    The array is displayed as a single channel, with a single lookup table applied. In this mode, there effective is no channel axis: all non-visible dimensions have sliders, and there is a single LUT control (the default_lut).

  • COMPOSITE (str) –

    Display all (or a subset of) channels as a composite image, with a different lookup table applied to each channel. In this mode, the slider for the channel axis is hidden by default, and LUT controls for each channel are shown.

  • COLOR (str) –

    Display a single channel at a time as a color image, with a channel-specific lookup table applied. In this mode, the slider for the channel axis is shown, and the user can select which channel to display. LUT controls are shown for all channels.

  • RGBA (str) –

    The array is displayed as an RGB image, with a single lookup table applied. In this mode, the slider for the channel axis is hidden, and a single LUT control is shown. Only valid when channel axis has length <= 4.

  • RGB (str) –

    Alias for RGBA.

Methods:

is_multichannel #

is_multichannel() -> bool

Return whether this mode displays multiple channels.

If is_multichannel is True, then the channel_axis slider should be hidden.

Source code in ndv/models/_array_display_model.py
100
101
102
103
104
105
def is_multichannel(self) -> bool:
    """Return whether this mode displays multiple channels.

    If `is_multichannel` is True, then the `channel_axis` slider should be hidden.
    """
    return self in (ChannelMode.COMPOSITE, ChannelMode.RGBA)

ClimPolicy #

Bases: BaseModel, ABC

ABC for contrast limit policies.

Methods:

  • get_limits

    Return the contrast limits for the given image.

Attributes:

cached_clims property #

cached_clims: Optional[tuple[float, float]]

Return the last calculated clims.

get_limits abstractmethod #

get_limits(image: NDArray) -> tuple[float, float]

Return the contrast limits for the given image.

Source code in ndv/models/_lut_model.py
31
32
33
@abstractmethod
def get_limits(self, image: npt.NDArray) -> tuple[float, float]:
    """Return the contrast limits for the given image."""

ClimsManual #

Bases: ClimPolicy

Manually specified contrast limits.

Attributes:

  • min (float) –

    The minimum contrast limit.

  • max (float) –

    The maximum contrast limit.

Attributes:

cached_clims property #

cached_clims: Optional[tuple[float, float]]

Return the last calculated clims.

ClimsMinMax #

Bases: ClimPolicy

Autoscale contrast limits based on the minimum and maximum values in the data.

Attributes:

cached_clims property #

cached_clims: Optional[tuple[float, float]]

Return the last calculated clims.

ClimsPercentile #

Bases: ClimPolicy

Autoscale contrast limits based on percentiles of the data.

Attributes:

  • min_percentile (float) –

    The lower percentile for the contrast limits.

  • max_percentile (float) –

    The upper percentile for the contrast limits.

Attributes:

cached_clims property #

cached_clims: Optional[tuple[float, float]]

Return the last calculated clims.

ClimsStdDev #

Bases: ClimPolicy

Automatically set contrast limits based on standard deviations from the mean.

Attributes:

  • n_stdev (float) –

    Number of standard deviations to use.

  • center (Optional[float]) –

    Center value for the standard deviation calculation. If None, the mean is used.

Attributes:

cached_clims property #

cached_clims: Optional[tuple[float, float]]

Return the last calculated clims.

DataWrapper #

DataWrapper(data: ArrayT)

Bases: Generic[ArrayT], ABC

Interface for wrapping different array-like data types.

DataWrapper.create() is a factory method that returns a DataWrapper instance for the given data type. If your datastore type is not supported, you may implement a new DataWrapper subclass to handle your data type. To do this, import and subclass DataWrapper, and (minimally) implement the supports and isel methods. Ensure that your class is imported before the DataWrapper.create method is called, and it will be automatically detected and used to wrap your data.

Methods:

  • clear_cache

    Clear any cached properties.

  • create

    Create a DataWrapper instance for the given data.

  • guess_channel_axis

    Return the (best guess) axis name for the channel dimension.

  • guess_z_axis

    Return the (best guess) axis name for the z (3rd spatial) dimension.

  • isel

    Return a slice of the data as a numpy array.

  • normalized_axis_key

    Return positive index for axis (which can be +/- int or str label).

  • sizes

    Return the sizes of the dimensions.

  • summary_info

    Return info label with information about the data.

  • supports

    Return True if this wrapper can handle the given object.

Attributes:

Source code in ndv/models/_data_wrapper.py
80
81
def __init__(self, data: ArrayT) -> None:
    self._data = data

axis_map cached property #

axis_map: Mapping[Hashable, int]

Mapping of ALL valid axis keys to normalized, positive integer keys.

coords abstractmethod property #

Return the coordinates for the data.

data property #

data: ArrayT

Return the data being wrapped.

dims abstractmethod property #

dims: tuple[Hashable, ...]

Return the dimension labels for the data.

dtype property #

dtype: dtype

Return the dtype for the data.

clear_cache #

clear_cache() -> None

Clear any cached properties.

Source code in ndv/models/_data_wrapper.py
259
260
261
262
def clear_cache(self) -> None:
    """Clear any cached properties."""
    if hasattr(self, "axis_map"):
        del self.axis_map

create classmethod #

create(data: ArrayT) -> DataWrapper[ArrayT]

Create a DataWrapper instance for the given data.

This method will detect all subclasses of DataWrapper and check them in order of their PRIORITY class variable. The first subclass that supports the given data will be used to wrap it.

Tip

This means that you can subclass DataWrapper to handle new data types. Just make sure that your subclass is imported before calling create.

If no subclasses support the data, a NotImplementedError is raised.

If an instance of DataWrapper is passed in, it will be returned as-is.

Source code in ndv/models/_data_wrapper.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
@classmethod
def create(cls, data: ArrayT) -> DataWrapper[ArrayT]:
    """Create a DataWrapper instance for the given data.

    This method will detect all subclasses of DataWrapper and check them in order of
    their `PRIORITY` class variable. The first subclass that
    [`supports`][ndv.DataWrapper.supports] the given data will be used to wrap it.

    !!! tip

        This means that you can subclass DataWrapper to handle new data types.
        Just make sure that your subclass is imported before calling `create`.

    If no subclasses support the data, a `NotImplementedError` is raised.

    If an instance of `DataWrapper` is passed in, it will be returned as-is.
    """
    if isinstance(data, DataWrapper):
        return data

    # check subclasses for support
    # This allows users to define their own DataWrapper subclasses which will
    # be automatically detected (assuming they have been imported by this point)
    for subclass in sorted(_recurse_subclasses(cls), key=lambda x: x.PRIORITY):
        try:
            if subclass.supports(data):
                logging.debug(f"Using {subclass.__name__} to wrap {type(data)}")
                return subclass(data)
        except Exception as e:
            warnings.warn(
                f"Error checking DataWrapper subclass {subclass.__name__}: {e}",
                RuntimeWarning,
                stacklevel=2,
            )
    raise NotImplementedError(f"Don't know how to wrap type {type(data)}")

guess_channel_axis #

guess_channel_axis() -> Hashable | None

Return the (best guess) axis name for the channel dimension.

Source code in ndv/models/_data_wrapper.py
186
187
188
189
190
191
192
193
194
195
196
197
def guess_channel_axis(self) -> Hashable | None:
    """Return the (best guess) axis name for the channel dimension."""
    # for arrays with labeled dimensions,
    # see if any of the dimensions are named "channel"
    sizes = self.sizes()
    for dimkey, val in sizes.items():
        if str(dimkey).lower() in self.COMMON_CHANNEL_NAMES:
            if val <= self.MAX_CHANNELS:
                return self.normalized_axis_key(dimkey)

    # otherwise use the smallest dimension as the channel axis
    return min(sizes, key=sizes.get)  # type: ignore [arg-type]

guess_z_axis #

guess_z_axis() -> Hashable | None

Return the (best guess) axis name for the z (3rd spatial) dimension.

Source code in ndv/models/_data_wrapper.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
def guess_z_axis(self) -> Hashable | None:
    """Return the (best guess) axis name for the z (3rd spatial) dimension."""
    sizes = self.sizes()
    ch = self.guess_channel_axis()
    for dimkey in sizes:
        if str(dimkey).lower() in self.COMMON_Z_AXIS_NAMES:
            if (normed := self.normalized_axis_key(dimkey)) != ch:
                return normed

    # otherwise return the LAST axis that is neither in the last two dimensions
    # or the channel axis guess
    return next(
        (self.normalized_axis_key(x) for x in reversed(self.dims[:-2]) if x != ch),
        None,
    )

isel abstractmethod #

isel(index: Mapping[int, int | slice]) -> ndarray

Return a slice of the data as a numpy array.

index will look like (e.g.) {0: slice(0, 10), 1: 5}.

Source code in ndv/models/_data_wrapper.py
104
105
106
107
108
109
@abstractmethod
def isel(self, index: Mapping[int, int | slice]) -> np.ndarray:
    """Return a slice of the data as a numpy array.

    `index` will look like (e.g.) `{0: slice(0, 10), 1: 5}`.
    """

normalized_axis_key #

normalized_axis_key(axis: Hashable) -> int

Return positive index for axis (which can be +/- int or str label).

Source code in ndv/models/_data_wrapper.py
247
248
249
250
251
252
253
254
255
256
257
def normalized_axis_key(self, axis: Hashable) -> int:
    """Return positive index for `axis` (which can be +/- int or str label)."""
    try:
        return self.axis_map[axis]
    except KeyError as e:
        ndims = len(self.dims)
        if isinstance(axis, int):
            raise IndexError(
                f"Axis index {axis} out of bounds for data with {ndims} dimensions"
            ) from e
        raise IndexError(f"Axis label {axis} not found in data dimensions") from e

sizes #

sizes() -> Mapping[Hashable, int]

Return the sizes of the dimensions.

Source code in ndv/models/_data_wrapper.py
179
180
181
def sizes(self) -> Mapping[Hashable, int]:
    """Return the sizes of the dimensions."""
    return {dim: len(self.coords[dim]) for dim in self.dims}

summary_info #

summary_info() -> str

Return info label with information about the data.

Source code in ndv/models/_data_wrapper.py
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def summary_info(self) -> str:
    """Return info label with information about the data."""
    package = getattr(self._data, "__module__", "").split(".")[0]
    info = f"{package}.{getattr(type(self._data), '__qualname__', '')}"

    if sizes := self.sizes():
        # if all of the dimension keys are just integers, omit them from size_str
        if all(isinstance(x, int) for x in sizes):
            size_str = repr(tuple(sizes.values()))
        # otherwise, include the keys in the size_str
        else:
            size_str = ", ".join(f"{k}:{v}" for k, v in sizes.items())
            size_str = f"({size_str})"
        info += f" {size_str}"
    if dtype := getattr(self._data, "dtype", ""):
        info += f", {dtype}"
    if nbytes := getattr(self._data, "nbytes", 0) / 1e6:
        info += f", {nbytes:.2f}MB"
    return info

supports abstractmethod classmethod #

supports(obj: Any) -> bool

Return True if this wrapper can handle the given object.

Any exceptions raised by this method will be suppressed, so it is safe to directly import necessary dependencies without a try/except block.

Source code in ndv/models/_data_wrapper.py
85
86
87
88
89
90
91
92
@classmethod
@abstractmethod
def supports(cls, obj: Any) -> bool:
    """Return True if this wrapper can handle the given object.

    Any exceptions raised by this method will be suppressed, so it is safe to
    directly import necessary dependencies without a try/except block.
    """

LUTModel #

Bases: NDVModel

Representation of how to display a channel of an array.

Attributes:

  • visible (bool) –

    Whether to display this channel. NOTE: This has implications for data retrieval, as we may not want to request channels that are not visible. See ArrayDisplayModel.current_index.

  • cmap (Colormap) –

    cmap.Colormap to use for this channel. This can be expressed as any channel. This can be expressed as any "colormap like" object

  • clims (Union[ClimsManual, ClimsPercentile, ClimsStdDev, ClimsMinMax]) –

    Method for determining the contrast limits for this channel. If a 2-element tuple or list is provided, it is interpreted as a manual contrast limit.

  • gamma (float) –

    Gamma applied to the data before applying the colormap. By default, 1.0.

NDVModel #

Bases: BaseModel

Base evented model for NDV models.

Uses pydantic.BaseModel and psygnal.SignalGroupDescriptor.