Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

datashade of NdOverlay of QuadMesh does not work #6204

Open
peterroelants opened this issue Apr 23, 2024 · 4 comments
Open

datashade of NdOverlay of QuadMesh does not work #6204

peterroelants opened this issue Apr 23, 2024 · 4 comments

Comments

@peterroelants
Copy link
Contributor

peterroelants commented Apr 23, 2024

Description of expected behavior and the observed behavior

Datashading an NdOverlay of Quadmeshes (with different axis bins) does not work and results in: ValueError: Supplied Image bounds do not match the coordinates defined in the data. Bounds only have to be declared if no coordinates are supplied, otherwise they must match the data. To change the displayed extents set the range on the x- and y-dimensions.

I tried setting hv.config.image_rtol = 1 but with no result

Complete, minimal, self-contained example code that reproduces the issue

import numpy as np
import xarray as xr


import datashader
import holoviews as hv
from holoviews.operation.datashader import datashade, shade, dynspread, spread, rasterize
import panel as pn
import param

hv.extension('bokeh', logo=False)
pn.extension(comms='vscode')
np.random.seed(1)

def generate_data(direction: str) -> xr.DataArray:
    # Non shared axis doesn't work
    x_axis = np.sort(np.random.rand(300)) * 20
    y_axis = np.sort(np.random.rand(50)) * 15
    # Shared axis works
    # x_axis = np.linspace(0, 20)
    # y_axis = np.linspace(0, 15)
    if direction == "vertical":
        data = np.sin(np.expand_dims(x_axis, -1) * np.expand_dims(np.ones_like(y_axis), 0))
    elif direction == "horizontal":
        data = np.sin(np.expand_dims(np.ones_like(x_axis), -1) * np.expand_dims(y_axis, 0))
    else:
        assert False
    da = xr.DataArray(
        data=data,
        name="D",
        dims=["x", "y"],
        coords={
            "x": x_axis,
            "y": y_axis,
        },
    )
    return da


samples = {
    k: generate_data(k)
    for k in ["vertical", "horizontal"]
}

meshes = {
    k: hv.QuadMesh(da.where(da>0), kdims=["x", "y"], vdims=["D"], label=k).opts(alpha=0.5)
    for k, da in samples.items()
}

overlay = hv.NdOverlay(meshes, kdims=["sample"], sort=False)

# Set rtol to high value
hv.config.image_rtol = 1

# Rasterize by itself works, but doesn't shade
# rasterize(overlay)
# Calling `datashade` does not work
# datashade(overlay)
shade(
    rasterize(overlay),
    aggregator=datashader.by("sample"),
)

Stack traceback and/or browser JavaScript console output

Stack trace
WARNING:param.RGB01051: RGB dimension(s) x and y are not evenly sampled to relative tolerance of 1. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the RGB constructor.
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/IPython/core/formatters.py:977](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/IPython/core/formatters.py#line=976), in MimeBundleFormatter.__call__(self, obj, include, exclude)
    974     method = get_real_method(obj, self.print_method)
    976     if method is not None:
--> 977         return method(include=include, exclude=exclude)
    978     return None
    979 else:

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/dimension.py:1286](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/dimension.py#line=1285), in Dimensioned._repr_mimebundle_(self, include, exclude)
   1279 def _repr_mimebundle_(self, include=None, exclude=None):
   1280     """
   1281     Resolves the class hierarchy for the class rendering the
   1282     object using any display hooks registered on Store.display
   1283     hooks.  The output of all registered display_hooks is then
   1284     combined and returned.
   1285     """
-> 1286     return Store.render(self)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/options.py:1428](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/options.py#line=1427), in Store.render(cls, obj)
   1426 data, metadata = {}, {}
   1427 for hook in hooks:
-> 1428     ret = hook(obj)
   1429     if ret is None:
   1430         continue

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:287](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py#line=286), in pprint_display(obj)
    285 if not ip.display_formatter.formatters['text[/plain](http://localhost:8888/plain)'].pprint:
    286     return None
--> 287 return display(obj, raw_output=True)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:261](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py#line=260), in display(obj, raw_output, **kwargs)
    259 elif isinstance(obj, (HoloMap, DynamicMap)):
    260     with option_state(obj):
--> 261         output = map_display(obj)
    262 elif isinstance(obj, Plot):
    263     output = render(obj)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:149](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py#line=148), in display_hook.<locals>.wrapped(element)
    147 try:
    148     max_frames = OutputSettings.options['max_frames']
--> 149     mimebundle = fn(element, max_frames=max_frames)
    150     if mimebundle is None:
    151         return {}, {}

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:209](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py#line=208), in map_display(vmap, max_frames)
    206     max_frame_warning(max_frames)
    207     return None
--> 209 return render(vmap)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:76](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py#line=75), in render(obj, **kwargs)
     73 if renderer.fig == 'pdf':
     74     renderer = renderer.instance(fig='png')
---> 76 return renderer.components(obj, **kwargs)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/renderer.py:397](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/renderer.py#line=396), in Renderer.components(self, obj, fmt, comm, **kwargs)
    395 if embed or config.comms == 'default':
    396     return self._render_panel(plot, embed, comm)
--> 397 return self._render_ipywidget(plot)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/renderer.py:419](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/renderer.py#line=418), in Renderer._render_ipywidget(self, plot)
    417 def _render_ipywidget(self, plot):
    418     # Handle rendering object as ipywidget
--> 419     widget = ipywidget(plot, combine_events=True)
    420     if hasattr(widget, '_repr_mimebundle_'):
    421         return widget._repr_mimebundle_(), {}

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/io/notebook.py:560](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/io/notebook.py#line=559), in ipywidget(obj, doc, **kwargs)
    558 from ..pane import panel
    559 doc = doc if doc else Document()
--> 560 model = panel(obj, **kwargs).get_root(doc=doc)
    561 widget = BokehModel(model, combine_events=True)
    562 if hasattr(widget, '_view_count'):

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/base.py:422](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/base.py#line=421), in PaneBase.get_root(self, doc, comm, preprocess)
    420         root = wrapper.get_root(doc, comm, preprocess)
    421 else:
--> 422     root_view, root = self._get_root_model(doc, comm, preprocess)
    423 ref = root.ref['id']
    424 state._views[ref] = (root_view, root, doc, comm)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/base.py:351](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/base.py#line=350), in PaneBase._get_root_model(self, doc, comm, preprocess)
    349     root_view = self
    350 else:
--> 351     root = self.layout._get_model(doc, comm=comm)
    352     root_view = self.layout
    353 if preprocess:

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/layout/base.py:186](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/layout/base.py#line=185), in Panel._get_model(self, doc, root, parent, comm)
    184 root = root or model
    185 self._models[root.ref['id']] = (model, parent)
--> 186 objects, _ = self._get_objects(model, [], doc, root, comm)
    187 props = self._get_properties(doc)
    188 props[self._property_mapping['objects']] = objects

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/layout/base.py:168](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/layout/base.py#line=167), in Panel._get_objects(self, model, old_objects, doc, root, comm)
    166 else:
    167     try:
--> 168         child = pane._get_model(doc, root, model, comm)
    169     except RerenderError as e:
    170         if e.layout is not None and e.layout is not self:

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/holoviews.py:429](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/holoviews.py#line=428), in HoloViews._get_model(self, doc, root, parent, comm)
    427     plot = self.object
    428 else:
--> 429     plot = self._render(doc, comm, root)
    431 plot.pane = self
    432 backend = plot.renderer.backend

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/holoviews.py:525](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/panel/pane/holoviews.py#line=524), in HoloViews._render(self, doc, comm, root)
    522     if comm:
    523         kwargs['comm'] = comm
--> 525 return renderer.get_plot(self.object, **kwargs)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/renderer.py:68](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/renderer.py#line=67), in BokehRenderer.get_plot(self_or_cls, obj, doc, renderer, **kwargs)
     61 @bothmethod
     62 def get_plot(self_or_cls, obj, doc=None, renderer=None, **kwargs):
     63     """
     64     Given a HoloViews Viewable return a corresponding plot instance.
     65     Allows supplying a document attach the plot to, useful when
     66     combining the bokeh model with another plot.
     67     """
---> 68     plot = super().get_plot(obj, doc, renderer, **kwargs)
     69     if plot.document is None:
     70         plot.document = Document() if self_or_cls.notebook_context else curdoc()

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/renderer.py:217](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/renderer.py#line=216), in Renderer.get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
    214     raise SkipRendering(msg.format(dims=dims))
    216 # Initialize DynamicMaps with first data item
--> 217 initialize_dynamic(obj)
    219 if not renderer:
    220     renderer = self_or_cls

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/util.py:270](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/util.py#line=269), in initialize_dynamic(obj)
    268     continue
    269 if not len(dmap):
--> 270     dmap[dmap._initial_key()]

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/spaces.py:1217](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/spaces.py#line=1216), in DynamicMap.__getitem__(self, key)
   1215 # Not a cross product and nothing cached so compute element.
   1216 if cache is not None: return cache
-> 1217 val = self._execute_callback(*tuple_key)
   1218 if data_slice:
   1219     val = self._dataslice(val, data_slice)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/spaces.py:984](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/spaces.py#line=983), in DynamicMap._execute_callback(self, *args)
    981     kwargs['_memoization_hash_'] = hash_items
    983 with dynamicmap_memoization(self.callback, self.streams):
--> 984     retval = self.callback(*args, **kwargs)
    985 return self._style(retval)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/spaces.py:552](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/spaces.py#line=551), in Callable.__call__(self, *args, **kwargs)
    550     return self.callable.rx.value
    551 elif not args and not kwargs and not any(kwarg_hash):
--> 552     return self.callable()
    553 inputs = [i for i in self.inputs if isinstance(i, DynamicMap)]
    554 streams = []

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/util/__init__.py:1038](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/util/__init__.py#line=1037), in Dynamic._dynamic_operation.<locals>.dynamic_operation(*key, **kwargs)
   1036 def dynamic_operation(*key, **kwargs):
   1037     key, obj = resolve(key, kwargs)
-> 1038     return apply(obj, *key, **kwargs)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/util/__init__.py:1030](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/util/__init__.py#line=1029), in Dynamic._dynamic_operation.<locals>.apply(element, *key, **kwargs)
   1028 def apply(element, *key, **kwargs):
   1029     kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs), **kwargs)
-> 1030     processed = self._process(element, key, kwargs)
   1031     if (self.p.link_dataset and isinstance(element, Dataset) and
   1032         isinstance(processed, Dataset) and processed._dataset is None):
   1033         processed._dataset = element.dataset

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/util/__init__.py:1012](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/util/__init__.py#line=1011), in Dynamic._process(self, element, key, kwargs)
   1010 elif isinstance(self.p.operation, Operation):
   1011     kwargs = {k: v for k, v in kwargs.items() if k in self.p.operation.param}
-> 1012     return self.p.operation.process_element(element, key, **kwargs)
   1013 else:
   1014     return self.p.operation(element, **kwargs)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/operation.py:194](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/operation.py#line=193), in Operation.process_element(self, element, key, **params)
    191 else:
    192     self.p = param.ParamOverrides(self, params,
    193                                   allow_extra_keywords=self._allow_extra_keywords)
--> 194 return self._apply(element, key)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/operation.py:141](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/core/operation.py#line=140), in Operation._apply(self, element, key)
    139     if not in_method:
    140         element._in_method = True
--> 141 ret = self._process(element, key)
    142 if hasattr(element, '_in_method') and not in_method:
    143     element._in_method = in_method

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/operation/datashader.py:1340](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/operation/datashader.py#line=1339), in shade._process(self, element, key)
   1338     else:
   1339         img = tf.shade(array, **shade_opts)
-> 1340 return RGB(self.uint32_to_uint8_xr(img), **params)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/element/raster.py:696](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/element/raster.py#line=695), in RGB.__init__(self, data, kdims, vdims, **params)
    690 if ((hasattr(data, 'shape') and data.shape[-1] == 4 and len(vdims) == 3) or
    691     (isinstance(data, tuple) and isinstance(data[-1], np.ndarray) and data[-1].ndim == 3
    692      and data[-1].shape[-1] == 4 and len(vdims) == 3) or
    693     (isinstance(data, dict) and tuple(dimension_name(vd) for vd in vdims)+(alpha.name,) in data)):
    694     # Handle all forms of packed value dimensions
    695     vdims.append(alpha)
--> 696 super().__init__(data, kdims=kdims, vdims=vdims, **params)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/element/raster.py:315](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/element/raster.py#line=314), in Image.__init__(self, data, kdims, vdims, bounds, extents, xdensity, ydensity, rtol, **params)
    313 if non_finite:
    314    self.bounds = BoundingBox(points=((np.nan, np.nan), (np.nan, np.nan)))
--> 315 self._validate(data_bounds, supplied_bounds)

File [~/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/element/raster.py:381](http://localhost:8888/home/peter/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/element/raster.py#line=380), in Image._validate(self, data_bounds, supplied_bounds)
    379         not_close = True
    380 if not_close:
--> 381     raise ValueError('Supplied Image bounds do not match the coordinates defined '
    382                      'in the data. Bounds only have to be declared if no coordinates '
    383                      'are supplied, otherwise they must match the data. To change '
    384                      'the displayed extents set the range on the x- and y-dimensions.')

ValueError: Supplied Image bounds do not match the coordinates defined in the data. Bounds only have to be declared if no coordinates are supplied, otherwise they must match the data. To change the displayed extents set the range on the x- and y-dimensions.

More info:

  • This does not happen when my QuadMeshes are uniformly spaced.
    • The problem is that my real-world data is also not uniformly spaced (I try to mock this here a a minimal reproducible example)
  • I tried increasing hv.config.image_rtol to 1, but to no avail. This thread led me to believe that the issue might be how datashader works?
  • The issue appears with both datashade( and shade(rasterize(. rasterize by itself seems to work fine (but doesn't shade the data, which I need).

ALL software version info

Python implementation: CPython
Python version       : 3.11.9
IPython version      : 8.23.0
holoviews : 1.18.3
param     : 2.1.0
numpy     : 1.26.4
panel     : 1.4.1
datashader: 0.16.1
xarray    : 2024.3.0
@hoxbro
Copy link
Member

hoxbro commented Apr 26, 2024

It seems to work if you use Overlay over 'NdOverlay`?

meshes = [
    hv.QuadMesh(da.where(da > 0), kdims=["x", "y"], vdims=["D"]).opts(alpha=0.5)
    for da in samples.values()
]
overlay = hv.Overlay(meshes, kdims=["sample"])
shade(
    rasterize(overlay),
    aggregator=datashader.by("sample"),
)

@peterroelants
Copy link
Contributor Author

It seems to work if you use Overlay over 'NdOverlay`?

Using Overlay instead of NdOverlay does not color the meshes ("sample" dimension) differently:
bokeh_plot

@hoxbro
Copy link
Member

hoxbro commented Apr 26, 2024

What about this?
 Screenshot 2024-04-26 15 59 50

@peterroelants
Copy link
Contributor Author

What about this?

Hey, thanks, however it still seems like it's missing some shading between the two groups.

I've been playing around with the image_rtol value and if I set it to something really large (e.g. hv.config.image_rtol = 1e6) it seems to work:

bokeh_plot (1)

However, I wonder what the implications are of setting hv.config's image_rtol to such a high value. Is image_rtol only used for validation and nothing else?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants