holoviz / datashader

Quickly and accurately render even the largest data.
http://datashader.org
BSD 3-Clause "New" or "Revised" License
3.3k stars 366 forks source link

rectilinear / curvilinear mesh rasterization #598

Closed rabernat closed 4 years ago

rabernat commented 6 years ago

Description

I normally use datashader via holoviews in the following way.

import holoviews as hv
from holoviews.operation.datashader import datashade
hv_ds = hv.Dataset(ds['temp'])
qm = hv_ds.to(hv.QuadMesh, kdims=["xt_ocean", "yt_ocean"], dynamic=True)
datashade(qm)

where ds is a very big xarray dataset.

I am converting to QuadMesh rather than Image because the grid spacing of my dataset is irregular. Specifically, in VTK speak, it is rectilinear mesh:

vtk datatypes

Shading the QuadMesh is extremely slow compared to Image. My understanding is that the QuadMesh is first internally converted to TriMesh by datashader. It should be possible to have a much more optimized path for rectilinear meshes, because of their inherent structure.

Your environment

cc @philippjfr

jbednar commented 6 years ago

Right now, datashader itself has no support for rectilinear meshes, and as you say the HoloViews trick of using Trimeshes via QuadMesh is not nearly as efficient as it could be. Supporting rectilinear meshes seems very feasible, and it may be relevant to a project we're working on, so I hope we will be able to address it relatively soon. But unfortunately this isn't something that we've already agreed and scheduled, so I can't predict precisely when it would come up next in priority.

jbednar commented 6 years ago

Update: We are hoping to get funding soon to add native quadmesh support, which should be closer in speed to the regularly spaced raster regridding than to the current slower trimesh-based approximation. So it's likely to be funded in 2018, but I cannot promise any particular date. We'll also be improving the trimesh speed at the same time, and adding dask support for both cases.

jbednar commented 4 years ago

Added in https://github.com/holoviz/datashader/pull/885 and available for testing via conda install -c pyviz/label/dev datashader=0.11.0a2

rabernat commented 4 years ago

I recently tried this again, assuming it had been fixed in the latest releases. I'm using holoviews version 1.13.3, and datashader version 0.10.0. Here is a MRE:

import hvplot.xarray
import holoviews as hv
from holoviews.operation.datashader import datashade
import xarray as xr
import numpy as np

da = xr.DataArray(np.random.rand(1000, 2000), dims=['y', 'x'], name='foo',
                  coords={'x': (['x'], np.arange(2000)),
                          'y': (['y'], np.arange(1000)**0.5)})
da = da.chunk() #use dask
hv_ds = hv.Dataset(da)
qm = hv_ds.to(hv.QuadMesh, kdims=["x", "y"], dynamic=True)
datashade(qm)

This raises the following error

WARNING:param.QuadMesh01455: Setting non-parameter attribute dynamic=True using a mechanism intended only for parameters
WARNING:param.dynamic_operation: Callable raised "TypingError('Failed in nopython mode pipeline (step: nopython frontend)\n\x1b[1m\x1b[1mnon-precise type pyobject\x1b[0m\n\x1b[0m\x1b[1mDuring: typing of argument at <extend_cpu> (4)\x1b[0m\n\x1b[1m\nFile "<extend_cpu>", line 4:\x1b[0m\n\x1b[1m<source missing, REPL/exec in use?>\x1b[0m\n\nThis error may have been caused by the following argument(s):\n- argument 4: \x1b[1mcannot determine Numba type of <class \'dask.array.core.Array\'>\x1b[0m\n')".
Invoked as dynamic_operation(height=400, scale=1.0, width=400, x_range=None, y_range=None)
---------------------------------------------------------------------------
TypingError                               Traceback (most recent call last)
/srv/conda/envs/notebook/lib/python3.7/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude)
    968 
    969             if method is not None:
--> 970                 return method(include=include, exclude=exclude)
    971             return None
    972         else:

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/dimension.py in _repr_mimebundle_(self, include, exclude)
   1310         combined and returned.
   1311         """
-> 1312         return Store.render(self)
   1313 
   1314 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/options.py in render(cls, obj)
   1393         data, metadata = {}, {}
   1394         for hook in hooks:
-> 1395             ret = hook(obj)
   1396             if ret is None:
   1397                 continue

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in pprint_display(obj)
    280     if not ip.display_formatter.formatters['text/plain'].pprint:
    281         return None
--> 282     return display(obj, raw_output=True)
    283 
    284 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in display(obj, raw_output, **kwargs)
    256     elif isinstance(obj, (HoloMap, DynamicMap)):
    257         with option_state(obj):
--> 258             output = map_display(obj)
    259     elif isinstance(obj, Plot):
    260         output = render(obj)

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in wrapped(element)
    144         try:
    145             max_frames = OutputSettings.options['max_frames']
--> 146             mimebundle = fn(element, max_frames=max_frames)
    147             if mimebundle is None:
    148                 return {}, {}

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in map_display(vmap, max_frames)
    204         return None
    205 
--> 206     return render(vmap)
    207 
    208 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in render(obj, **kwargs)
     66         renderer = renderer.instance(fig='png')
     67 
---> 68     return renderer.components(obj, **kwargs)
     69 
     70 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/plotting/renderer.py in components(self, obj, fmt, comm, **kwargs)
    386                 doc = Document()
    387                 with config.set(embed=embed):
--> 388                     model = plot.layout._render_model(doc, comm)
    389                 if embed:
    390                     return render_model(model, comm)

/srv/conda/envs/notebook/lib/python3.7/site-packages/panel/viewable.py in _render_model(self, doc, comm)
    416         if comm is None:
    417             comm = state._comm_manager.get_server_comm()
--> 418         model = self.get_root(doc, comm)
    419 
    420         if config.embed:

/srv/conda/envs/notebook/lib/python3.7/site-packages/panel/viewable.py in get_root(self, doc, comm)
    645         """
    646         doc = doc or _curdoc()
--> 647         root = self._get_model(doc, comm=comm)
    648         self._preprocess(root)
    649         ref = root.ref['id']

/srv/conda/envs/notebook/lib/python3.7/site-packages/panel/layout.py in _get_model(self, doc, root, parent, comm)
    118         if root is None:
    119             root = model
--> 120         objects = self._get_objects(model, [], doc, root, comm)
    121         props = dict(self._init_properties(), objects=objects)
    122         model.update(**self._process_param_change(props))

/srv/conda/envs/notebook/lib/python3.7/site-packages/panel/layout.py in _get_objects(self, model, old_objects, doc, root, comm)
    108             else:
    109                 try:
--> 110                     child = pane._get_model(doc, root, model, comm)
    111                 except RerenderError:
    112                     return self._get_objects(model, current_objects[:i], doc, root, comm)

/srv/conda/envs/notebook/lib/python3.7/site-packages/panel/pane/holoviews.py in _get_model(self, doc, root, parent, comm)
    225             plot = self.object
    226         else:
--> 227             plot = self._render(doc, comm, root)
    228 
    229         plot.pane = self

/srv/conda/envs/notebook/lib/python3.7/site-packages/panel/pane/holoviews.py in _render(self, doc, comm, root)
    284             kwargs = {}
    285 
--> 286         return renderer.get_plot(self.object, **kwargs)
    287 
    288     def _cleanup(self, root):

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/plotting/bokeh/renderer.py in get_plot(self_or_cls, obj, doc, renderer, **kwargs)
     71         combining the bokeh model with another plot.
     72         """
---> 73         plot = super(BokehRenderer, self_or_cls).get_plot(obj, doc, renderer, **kwargs)
     74         if plot.document is None:
     75             plot.document = Document() if self_or_cls.notebook_context else curdoc()

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/plotting/renderer.py in get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
    214 
    215         # Initialize DynamicMaps with first data item
--> 216         initialize_dynamic(obj)
    217 
    218         if not renderer:

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/plotting/util.py in initialize_dynamic(obj)
    249             continue
    250         if not len(dmap):
--> 251             dmap[dmap._initial_key()]
    252 
    253 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/spaces.py in __getitem__(self, key)
   1285         # Not a cross product and nothing cached so compute element.
   1286         if cache is not None: return cache
-> 1287         val = self._execute_callback(*tuple_key)
   1288         if data_slice:
   1289             val = self._dataslice(val, data_slice)

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/spaces.py in _execute_callback(self, *args)
   1059 
   1060         with dynamicmap_memoization(self.callback, self.streams):
-> 1061             retval = self.callback(*args, **kwargs)
   1062         return self._style(retval)
   1063 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/spaces.py in __call__(self, *args, **kwargs)
    693 
    694         try:
--> 695             ret = self.callable(*args, **kwargs)
    696         except KeyError:
    697             # KeyError is caught separately because it is used to signal

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/util/__init__.py in dynamic_operation(*key, **kwargs)
   1008         def dynamic_operation(*key, **kwargs):
   1009             key, obj = resolve(key, kwargs)
-> 1010             return apply(obj, *key, **kwargs)
   1011 
   1012         operation = self.p.operation

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/util/__init__.py in apply(element, *key, **kwargs)
   1000         def apply(element, *key, **kwargs):
   1001             kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs), **kwargs)
-> 1002             processed = self._process(element, key, kwargs)
   1003             if (self.p.link_dataset and isinstance(element, Dataset) and
   1004                 isinstance(processed, Dataset) and processed._dataset is None):

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/util/__init__.py in _process(self, element, key, kwargs)
    982         elif isinstance(self.p.operation, Operation):
    983             kwargs = {k: v for k, v in kwargs.items() if k in self.p.operation.param}
--> 984             return self.p.operation.process_element(element, key, **kwargs)
    985         else:
    986             return self.p.operation(element, **kwargs)

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/operation.py in process_element(self, element, key, **params)
    172             self.p = param.ParamOverrides(self, params,
    173                                           allow_extra_keywords=self._allow_extra_keywords)
--> 174         return self._apply(element, key)
    175 
    176 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/operation.py in _apply(self, element, key)
    130 
    131         element_pipeline = getattr(element, '_pipeline', None)
--> 132         ret = self._process(element, key)
    133 
    134         for hook in self._postprocess_hooks:

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/operation/datashader.py in _process(self, element, key)
   1442 
   1443     def _process(self, element, key=None):
-> 1444         agg = rasterize._process(self, element, key)
   1445         shaded = shade._process(self, agg, key)
   1446         return shaded

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/operation/datashader.py in _process(self, element, key)
   1421                                        if k in transform.param})
   1422             op._precomputed = self._precomputed
-> 1423             element = element.map(op, predicate)
   1424             self._precomputed = op._precomputed
   1425 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/data/__init__.py in pipelined_fn(*args, **kwargs)
    214 
    215             try:
--> 216                 result = method_fn(*args, **kwargs)
    217 
    218                 op = method_op.instance(

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/data/__init__.py in map(self, *args, **kwargs)
   1181 
   1182     def map(self, *args, **kwargs):
-> 1183         return super(Dataset, self).map(*args, **kwargs)
   1184     map.__doc__ = LabelledData.map.__doc__
   1185 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/dimension.py in map(self, map_fn, specs, clone)
    703             return deep_mapped
    704         else:
--> 705             return map_fn(self) if applies else self
    706 
    707 

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/operation.py in __call__(self, element, **kwargs)
    192             elif ((self._per_element and isinstance(element, Element)) or
    193                   (not self._per_element and isinstance(element, ViewableElement))):
--> 194                 return self._apply(element)
    195         elif 'streams' not in kwargs:
    196             kwargs['streams'] = self.p.streams

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/core/operation.py in _apply(self, element, key)
    130 
    131         element_pipeline = getattr(element, '_pipeline', None)
--> 132         ret = self._process(element, key)
    133 
    134         for hook in self._postprocess_hooks:

/srv/conda/envs/notebook/lib/python3.7/site-packages/holoviews/operation/datashader.py in _process(self, element, key)
   1089 
   1090         vdim = getattr(agg_fn, 'column', element.vdims[0].name)
-> 1091         agg = cvs.quadmesh(data[vdim], x.name, y.name, agg_fn)
   1092         xdim, ydim = list(agg.dims)[:2][::-1]
   1093         if xtype == "datetime":

/srv/conda/envs/notebook/lib/python3.7/site-packages/datashader/core.py in quadmesh(self, source, x, y, agg)
    770                 dims=list(xarr.dims)))
    771 
--> 772         return bypixel(source, self, glyph, agg)
    773 
    774     # TODO re 'untested', below: Consider replacing with e.g. a 3x3

/srv/conda/envs/notebook/lib/python3.7/site-packages/datashader/core.py in bypixel(source, canvas, glyph, agg)
   1159     with np.warnings.catch_warnings():
   1160         np.warnings.filterwarnings('ignore', r'All-NaN (slice|axis) encountered')
-> 1161         return bypixel.pipeline(source, schema, canvas, glyph, agg)
   1162 
   1163 

/srv/conda/envs/notebook/lib/python3.7/site-packages/datashader/utils.py in __call__(self, head, *rest, **kwargs)
     91         typ = type(head)
     92         if typ in lk:
---> 93             return lk[typ](head, *rest, **kwargs)
     94         for cls in getmro(typ)[1:]:
     95             if cls in lk:

/srv/conda/envs/notebook/lib/python3.7/site-packages/datashader/data_libraries/xarray.py in xarray_pipeline(df, schema, canvas, glyph, summary)
     17 def xarray_pipeline(df, schema, canvas, glyph, summary):
     18     cuda = cupy and isinstance(df[glyph.name].data, cupy.ndarray)
---> 19     return glyph_dispatch(glyph, df, schema, canvas, summary, cuda)
     20 
     21 

/srv/conda/envs/notebook/lib/python3.7/site-packages/datashader/utils.py in __call__(self, head, *rest, **kwargs)
     94         for cls in getmro(typ)[1:]:
     95             if cls in lk:
---> 96                 return lk[cls](head, *rest, **kwargs)
     97         raise TypeError("No dispatch for {0} type".format(typ))
     98 

/srv/conda/envs/notebook/lib/python3.7/site-packages/datashader/data_libraries/pandas.py in default(glyph, source, schema, canvas, summary, cuda)
     43 
     44     bases = create((height, width))
---> 45     extend(bases, source, x_st + y_st, x_range + y_range)
     46 
     47     return finalize(bases,

/srv/conda/envs/notebook/lib/python3.7/site-packages/datashader/glyphs/quadmesh.py in extend(aggs, xr_ds, vt, bounds)
    186                 do_extend = extend_cpu
    187 
--> 188             do_extend(xs, ys, *aggs_and_cols)
    189 
    190         return extend

/srv/conda/envs/notebook/lib/python3.7/site-packages/numba/core/dispatcher.py in _compile_for_args(self, *args, **kws)
    413                 e.patch_message(msg)
    414 
--> 415             error_rewrite(e, 'typing')
    416         except errors.UnsupportedError as e:
    417             # Something unsupported is present in the user code, add help info

/srv/conda/envs/notebook/lib/python3.7/site-packages/numba/core/dispatcher.py in error_rewrite(e, issue_type)
    356                 raise e
    357             else:
--> 358                 reraise(type(e), e, None)
    359 
    360         argtypes = []

/srv/conda/envs/notebook/lib/python3.7/site-packages/numba/core/utils.py in reraise(tp, value, tb)
     78         value = tp()
     79     if value.__traceback__ is not tb:
---> 80         raise value.with_traceback(tb)
     81     raise value
     82 

TypingError: Failed in nopython mode pipeline (step: nopython frontend)
non-precise type pyobject
During: typing of argument at <extend_cpu> (4)

File "<extend_cpu>", line 4:
<source missing, REPL/exec in use?>

This error may have been caused by the following argument(s):
- argument 4: cannot determine Numba type of <class 'dask.array.core.Array'>

Am I mistaken in assuming this should work with dask?

philippjfr commented 4 years ago

Can you try upgrading to Datashader 0.11 where this was fixed? There's been some annoying regressions in numba 0.49 and 0.50 which may have prevented you from auto-upgrading because Datashader pinned older versions.