hvplot icon indicating copy to clipboard operation
hvplot copied to clipboard

`robust` or `clim_percentile` option to get the 2nd / 98th percentile

Open ahuang11 opened this issue 6 months ago • 16 comments

from pystac_client import Client
from odc.stac import load
import numpy as np

client = Client.open("https://earth-search.aws.element84.com/v1")
collection = "sentinel-2-l2a"
tas_bbox = [146.5, -43.6, 146.7, -43.4]
search = client.search(collections=[collection], bbox=tas_bbox, datetime="2023-12")

ds = load(search.items(), bbox=tas_bbox, groupby="solar_day", chunks={})
da = ds[["red", "green", "blue"]].isel(time=2).load().to_array()
da.plot.imshow(robust=True)
image

Without robust: image

Which is what hvplot yields. image

When I try to manually calculate it and pass a custom clim, there's no effect https://github.com/pydata/xarray/blob/492aa076bd1812af71d68dc221312fb9a199d2d3/xarray/plot/utils.py#L215-L225

ahuang11 avatar Jan 05 '24 22:01 ahuang11

HoloViews has the clim_percentile plot option but that only applies to plot types that inherit from ColorbarPlot, while RGBPlot inherits from LegendPlot. @ahuang11 I'd say we should first add this plot option to HoloViews? To eventually expose it in hvPlot, where I'd say robust would be a good choice given that robust=True would be already familiar to Xarray users.

maximlt avatar Jan 08 '24 09:01 maximlt

I was looking at https://github.com/holoviz/holoviews/blob/6aa6c9ddb7c9308531753efae41cbd0873c975a3/holoviews/plotting/bokeh/raster.py#L134 and I'm not sure there's a way to add clim_percentile to RGBPlot without updating Bokeh too.

I tried doing:

        if self.clim_percentile:
            low, high = 0, 1
            style["color_mapper"] = LinearColorMapper(low=low, high=high)
AttributeError [Call holoviews.ipython.show_traceback() for details]
unexpected attribute 'color_mapper' to ImageRGBA, possible attributes are anchor, decorations, dh, dh_units, dilate, dw, dw_units, global_alpha, image, js_event_callbacks, js_property_callbacks, name, origin, subscribed_events, syncable, tags, x or y

I also tried doing:

        if self.clim_percentile:
            low, high = np.nanpercentile(img, (2, 98))
            img[img < low] = low
            img[img > high] = high

But it doesn't seem to work image

ahuang11 avatar Jan 22 '24 21:01 ahuang11

Thoughts about having this in hvPlot? https://github.com/holoviz/holoviews/pull/6137

I don't have a strong preference, but I do want to have a way forward.

ahuang11 avatar Mar 01 '24 01:03 ahuang11

I also don't have strong preference :)

maximlt avatar Mar 01 '24 13:03 maximlt

I also don't have strong preference :upside_down_face:

hoxbro avatar Mar 01 '24 15:03 hoxbro

I'm -1 on adding this anywhere. Taking percentiles of RGB values just does not make sense to me.

philippjfr avatar Mar 01 '24 17:03 philippjfr

If that's the case, how should users address this?

The initial goal was that I wanted to write a demo of how you can explore satellite imagery with hvplot easily, but then I encountered the issue that robust/clim_percentile was not exposed.

If robust/clim_percentile isn't implemented, its a tad tedious for the user to have to manually implement it for satellite imagery vs typing robust=True.

Would you prefer it be an operation? E.g. Clip?

ahuang11 avatar Mar 01 '24 17:03 ahuang11

Will have to look at it.

philippjfr avatar Mar 01 '24 17:03 philippjfr

Yeah, looking at this dataset the values are not R G B values they are raw reflectance values for different spectra from a satellite. Visualizing them using an RGB element without first processing these values is simply incorrect. So the only thing I can imagine doing is providing some operation in geoviews.

philippjfr avatar Mar 01 '24 17:03 philippjfr

https://ecampusontario.pressbooks.pub/remotesensing/chapter/chapter-5-visualization-and-manipulation-of-satellite-images/

philippjfr avatar Mar 01 '24 17:03 philippjfr

Based on my reading of that the transformation is quite simple, you divide the red green blue values 2**8 and then cast to uint8.

philippjfr avatar Mar 01 '24 17:03 philippjfr

Alternatively you can also apply histogram equalization.

philippjfr avatar Mar 01 '24 18:03 philippjfr

Hmm, the division thing doesn't work.

from datashader.transfer_functions import eq_hist
hv.RGB(eq_hist(da.data)[0].transpose([1, 2, 0])).opts(width=600)

bokeh_plot - 2024-03-02T112323 123

philippjfr avatar Mar 02 '24 10:03 philippjfr

hv.RGB((da / (2**2)).clip(0, 255).astype(np.uint8).data.transpose([1, 2, 0])).opts(width=600)

bokeh_plot - 2024-03-02T112755 242

philippjfr avatar Mar 02 '24 10:03 philippjfr

Okay, I've come around I'd be okay with a two-pronged approach, we implement the robust semantics in hvPlot and then HoloViews appropriately clips the data.

philippjfr avatar Mar 02 '24 10:03 philippjfr

Philipp found the actual _rescale_imshow_rgb robust usage in xarray here

ahuang11 avatar Mar 04 '24 16:03 ahuang11