Skip to content

Document how to perform on-the-fly normalization for channels. #460

Closed

Description

@LucaMarconato

Original comment from @MeyerBender

  • normalization of channels: see differences between the two notebooks when plotting all channels but DAPI. I guess it is intentional that no normalization is performed by default, and it should be possible to normalize channels using the “norm” argument, but the documentation isn’t very clear on how to do this properly.

The comment refers to the different between making plot with spatialproteomics and making plot with spatialdata-plot.

In spatialmuon we had a preprocessing: Callable argument. Maybe we need something like this here instead of norm? @timtreis

Activity

  1. MeyerBender commented on May 14, 2025

    @MeyerBender
    Contributor

    Example code snippet:

    import spatialdata as sd
    import spatialdata_plot
    from spatialdata.datasets import blobs
    from matplotlib.colors import Normalize
    
    sdata_blobs = blobs()
    # making the first (red) channel have a different range (0-0.05) than the other two (0-1)
    img = sdata_blobs.images['blobs_image'].values.copy()
    img[0] /= 20
    image = sd.models.Image2DModel.parse(
        img, transformations=None, dims=("c", "x", "y"), c_coords=[0, 1, 2]
    )
    sdata = sd.SpatialData(images={"image": image})
    
    fig, ax = plt.subplots(1, 3)
    sdata_blobs.pl.render_images(channel=[0, 1, 2]).pl.show(ax=ax[0])
    sdata.pl.render_images(channel=[0, 1, 2]).pl.show(ax=ax[1])
    
    # global normalization
    sdata.pl.render_images(channel=[0, 1, 2], norm=Normalize(vmin=img.min(), vmax=img.max())).pl.show(ax=ax[2])
    
    # TODO: what about channel-wise normalization?
    
    Image

    It would be nice to have the option to normalize so that the final plot for sdata looks the same as for sdata_blobs.

  2. pakiessling commented on Jul 26, 2025

    @pakiessling

    I would also be very much interested in normalization per channel.
    Protein data is super noisy and has vastly different ranges.

    This could maybe be done by supplying a dictionary of channel names and norms?
    I would also like to have an option to specify percentiles a la ("p20","p98") per channel similar to scanpy.pl.umap()

    I will try to see if I can get this to work

  3. timtreis commented on Jul 28, 2025

    @timtreis
    Member

    Would something like this help for your usecase?

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.colors import Normalize
    
    # pick channels and compute per-channel percentiles from the data
    channels = [0, 1, 2]
    arr = sdata.images["image"]  # xarray DataArray with dims ("c", "x", "y")
    
    norms = []
    for c in channels:
        a = arr.sel(c=c).values
        lo = np.nanpercentile(a, 2)
        hi = np.nanpercentile(a, 98)
        norms.append(Normalize(vmin=lo, vmax=hi, clip=True))
    
    sdata.pl.render_images(channel=channels, norm=norms, cmap=[plt.cm.gray]*len(channels)).pl.show()
    
    

    I think this should work, it's just less elegant since the order of the normalisation objects is only implicitly aligned with the order of the channels, not by name or anything like that

  4. pakiessling commented on Aug 4, 2025

    @pakiessling

    Thanks @timtreis ,

    however this does not seem to be currently supported:

    import matplotlib.pyplot as plt
    from matplotlib.colors import Normalize
    from spatialdata.datasets import blobs
    import spatialdata_plot
    spatialdata_plot.__version__
    >'0.2.10'
    
    sdata = blobs()
    
    # pick channels and compute per-channel percentiles from the data
    channels = [0, 1, 2]
    
    arr = sdata.images["blobs_image"]  # xarray DataArray with dims ("c", "x", "y")
    norms = []
    for c in channels:
        a = arr.sel(c=c).values
        lo = np.nanpercentile(a, 2)
        hi = np.nanpercentile(a, 98)
        norms.append(Normalize(vmin=lo, vmax=hi, clip=True))
    
    sdata.pl.render_images(channel=channels, norm=norms, cmap=[plt.cm.gray] * len(channels)).pl.show()
    
    
    --> 524 params_dict = _validate_image_render_params(
        525     self._sdata,
        526     element=element,
        527     channel=channel,
        528     alpha=alpha,
        529     palette=palette,
    ...
    -> 1621         raise TypeError("Parameter 'norm' must be of type Normalize.")
       1622     if element_type in ["shapes", "points"] and not isinstance(norm, bool | Normalize):
       1623         raise TypeError("Parameter 'norm' must be a boolean or a mpl.Normalize.")
    
    TypeError: Parameter 'norm' must be of type Normalize.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions