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

Add quadmesh glyph with rectilinear and curvilinear support #779

Merged
merged 24 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0d7d6ac
Add rectilinear quadmesh glyph and test
jonmmease Aug 12, 2019
eecca81
flake
jonmmease Aug 13, 2019
43ff657
py2 absolute_import
jonmmease Aug 13, 2019
0334bf4
py2 absolute_import
jonmmease Aug 13, 2019
711c40d
py2 absolute_import
jonmmease Aug 13, 2019
c0d1dd4
get default dimensions from DataArray
jonmmease Aug 13, 2019
6e8dcb4
Pull masking and int conversion outside of _extend function
jonmmease Aug 14, 2019
2afa24e
Add inline mean comment
jonmmease Aug 14, 2019
8edeb54
Pull x calculations outside of inner loop
jonmmease Aug 14, 2019
f4c6843
Fix clipping error
jonmmease Aug 15, 2019
87cf7e3
Specify explicit coordinates ordering
jonmmease Aug 15, 2019
223f4d0
Fix count_cat finalize method now that coords is an OrderedDict
jonmmease Aug 15, 2019
a0c1c1b
Fix dask construction of DataArray to use OrderedDict for coords
jonmmease Aug 15, 2019
4425e52
Remove print
jonmmease Aug 15, 2019
e260cea
Merge branch 'master' into quadmesh_glyph
jonmmease Aug 15, 2019
eaa410f
Use expand_aggs_and_cols decorator for quadmesh rendering
jonmmease Aug 15, 2019
0523dd2
Add initial Curvilinear implementation
jonmmease Aug 16, 2019
2d1d1c0
Switch to raycasting inclusion test
jonmmease Aug 16, 2019
0095ab5
Speedup by initializing numpy arrays outside main loop
jonmmease Aug 16, 2019
51029f8
Don't skip rendering last row/col in canvas
jonmmease Aug 16, 2019
d5005b2
Transpose Dataset dimensions to match coordinate dimensions
jonmmease Aug 16, 2019
fa57e48
Fix subpixel rendering to always display single pixel quads
jonmmease Aug 16, 2019
5a3372a
Curvilinear Tests
jonmmease Aug 16, 2019
33faae1
Remove unused quadmesh utility functions
jonmmease Aug 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions datashader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from . import transfer_functions as tf # noqa (API import)

from . import pandas # noqa (build backend dispatch)
from . import xarray # noqa (build backend dispatch)
try:
from . import dask # noqa (build backend dispatch)
except ImportError:
Expand Down
81 changes: 78 additions & 3 deletions datashader/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from collections import OrderedDict

from datashader.spatial.points import SpatialPointsFrame
from .utils import Dispatcher, ngjit, calc_res, calc_bbox, orient_array, compute_coords
from .utils import Dispatcher, ngjit, calc_res, calc_bbox, orient_array, \
compute_coords, dshape_from_xarray_dataset
from .utils import get_indices, dshape_from_pandas, dshape_from_dask
from .utils import Expr # noqa (API import)
from .resampling import resample_2d, resample_2d_distributed
Expand Down Expand Up @@ -564,6 +565,75 @@ def area(self, source, x, y, agg=None, axis=0, y_stack=None):

return bypixel(source, self, glyph, agg)

def quadmesh(self, source, x=None, y=None, agg=None):
"""Samples a recti- or curvi-linear quadmesh by canvas size and bounds.
Parameters
----------
source : xarray.DataArray or Dataset
The input datasource.
x, y : str
Column names for the x and y coordinates of each point.
agg : Reduction, optional
Reduction to compute. Default is ``mean()``.
Returns
-------
data : xarray.DataArray
"""
from .glyphs import QuadMeshRectilinear, QuadMeshCurvialinear

# Determine reduction operation
from .reductions import mean as mean_rnd

if isinstance(source, Dataset):
if agg is None or agg.column is None:
name = list(source.data_vars)[0]
else:
name = agg.column
# Keep as dataset so that source[agg.column] works
source = source[[name]]
elif isinstance(source, DataArray):
# Make dataset so that source[agg.column] works
name = source.name
source = source.to_dataset()
else:
raise ValueError("Invalid input type")

if agg is None:
agg = mean_rnd(name)

if x is None and y is None:
y, x = source[name].dims
elif not x or not y:
raise ValueError("Either specify both x and y coordinates"
"or allow them to be inferred.")
yarr, xarr = source[y], source[x]

if (yarr.ndim > 1 or xarr.ndim > 1) and xarr.dims != yarr.dims:
raise ValueError("Ensure that x- and y-coordinate arrays "
"share the same dimensions. x-coordinates "
"are indexed by %s dims while "
"y-coordinates are indexed by %s dims." %
(xarr.dims, yarr.dims))

if (name is not None
and agg.column is not None
and agg.column != name):
raise ValueError('DataArray name %r does not match '
'supplied reduction %s.' %
(source.name, agg))

if xarr.ndim == 1:
glyph = QuadMeshRectilinear(x, y, name)
elif xarr.ndim == 2:
glyph = QuadMeshCurvialinear(x, y, name)
else:
raise ValueError("""\
x- and y-coordinate arrays must have 1 or 2 dimensions.
Received arrays with dimensions: {dims}""".format(
dims=list(xarr.dims)))

return bypixel(source, self, glyph, agg)

# TODO re 'untested', below: Consider replacing with e.g. a 3x3
# array in the call to Canvas (plot_height=3,plot_width=3), then
# show the output as a numpy array that has a compact
Expand Down Expand Up @@ -910,11 +980,13 @@ def bypixel(source, canvas, glyph, agg):
glyph : Glyph
agg : Reduction
"""
if isinstance(source, DataArray):

# Convert 1D xarray DataArrays and DataSets into Dask DataFrames
if isinstance(source, DataArray) and source.ndim == 1:
if not source.name:
source.name = 'value'
source = source.reset_coords()
if isinstance(source, Dataset):
if isinstance(source, Dataset) and len(source.dims) == 1:
columns = list(source.coords.keys()) + list(source.data_vars.keys())
cols_to_keep = _cols_to_keep(columns, glyph, agg)
source = source.drop([col for col in columns if col not in cols_to_keep])
Expand All @@ -931,6 +1003,9 @@ def bypixel(source, canvas, glyph, agg):
dshape = dshape_from_pandas(source)
elif isinstance(source, dd.DataFrame):
dshape = dshape_from_dask(source)
elif isinstance(source, Dataset):
# Multi-dimensional Dataset
dshape = dshape_from_xarray_dataset(source)
else:
raise ValueError("source must be a pandas or dask DataFrame")
schema = dshape.measure
Expand Down
3 changes: 2 additions & 1 deletion datashader/dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dask
import pandas as pd
import dask.dataframe as dd
from collections import OrderedDict
from dask.base import tokenize, compute

from .core import bypixel
Expand Down Expand Up @@ -51,7 +52,7 @@ def shape_bounds_st_and_axis(df, canvas, glyph):

x_axis = canvas.x_axis.compute_index(x_st, width)
y_axis = canvas.y_axis.compute_index(y_st, height)
axis = [y_axis, x_axis]
axis = OrderedDict([(glyph.x_label, x_axis), (glyph.y_label, y_axis)])

return shape, bounds, st, axis

Expand Down
3 changes: 3 additions & 0 deletions datashader/glyphs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@
AreaToLineAxis1Ragged,
)
from .trimesh import Triangles # noqa (API import)
from .quadmesh import ( # noqa (API import)
QuadMeshRectilinear, QuadMeshCurvialinear
)
from .glyph import Glyph # noqa (API import)
16 changes: 16 additions & 0 deletions datashader/glyphs/glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ def _compute_y_bounds(ys):

return minval, maxval

@staticmethod
@ngjit
def _compute_bounds_2d(vals):
minval = np.inf
maxval = -np.inf
for i in range(vals.shape[0]):
for j in range(vals.shape[1]):
v = vals[i][j]
if not np.isnan(v):
if v < minval:
minval = v
if v > maxval:
maxval = v

return minval, maxval

def expand_aggs_and_cols(self, append):
"""
Create a decorator that can be used on functions that accept
Expand Down
Loading