spatialdata-plot
spatialdata-plot copied to clipboard
Document how to perform on-the-fly normalization for channels.
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
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?
It would be nice to have the option to normalize so that the final plot for sdata looks the same as for sdata_blobs.
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
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
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.