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 robust keyword #1291

Merged
merged 9 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions doc/user_guide/Customization.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
" least one dimension of the plot is left undefined, e.g. when\n",
" width and height or width and aspect are set the plot is set\n",
" to a fixed size, ignoring any responsive option.\n",
" robust: bool\n",
" If True and clim are absent, the colormap range is computed\n",
" with 2nd and 98th percentiles instead of the extreme values\n",
" for image elements. For RGB elements, clips the \"RGB\", or\n",
" raw reflectance values between 2nd and 98th percentiles.\n",
" Follows the same logic as xarray's robust option.\n",
" rot: number\n",
" Rotates the axis ticks along the x-axis by the specified\n",
" number of degrees.\n",
Expand Down
28 changes: 24 additions & 4 deletions hvplot/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ class HoloViewsConverter:
least one dimension of the plot is left undefined, e.g. when
width and height or width and aspect are set the plot is set
to a fixed size, ignoring any responsive option.
robust: bool
If True and clim are absent, the colormap range is computed
with 2nd and 98th percentiles instead of the extreme values
for image elements. For RGB elements, clips the "RGB", or
raw reflectance values between 2nd and 98th percentiles.
Follows the same logic as xarray's robust option.
rot: number
Rotates the axis ticks along the x-axis by the specified
number of degrees.
Expand Down Expand Up @@ -382,7 +388,7 @@ def __init__(
group_label=None, value_label='value', backlog=1000,
persist=False, use_dask=False, crs=None, fields={},
groupby=None, dynamic=True, grid=None, legend=None, rot=None,
title=None, xlim=None, ylim=None, clim=None, symmetric=None,
title=None, xlim=None, ylim=None, clim=None, robust=None, symmetric=None,
logx=None, logy=None, loglog=None, hover=None, subplots=False,
label=None, invert=False, stacked=False, colorbar=None,
datashade=False, rasterize=False, downsample=None,
Expand All @@ -405,7 +411,7 @@ def __init__(
self._process_data(
kind, data, x, y, by, groupby, row, col, use_dask,
persist, backlog, label, group_label, value_label,
hover_cols, attr_labels, transforms, stream, kwds
hover_cols, attr_labels, transforms, stream, robust, kwds
)

self.dynamic = dynamic
Expand Down Expand Up @@ -615,7 +621,8 @@ def __init__(
self._style_opts['cmap'] = self._default_cmaps['diverging']
else:
self._style_opts['cmap'] = self._default_cmaps['linear']

if robust:
plot_opts['clim_percentile'] = True
if symmetric is not None:
plot_opts['symmetric'] = symmetric
except TypeError:
Expand Down Expand Up @@ -695,7 +702,7 @@ def _process_crs(self, data, crs):
def _process_data(self, kind, data, x, y, by, groupby, row, col,
use_dask, persist, backlog, label, group_label,
value_label, hover_cols, attr_labels, transforms,
stream, kwds):
stream, robust, kwds):
gridded = kind in self._gridded_types
gridded_data = False
da = None
Expand Down Expand Up @@ -819,6 +826,19 @@ def _process_data(self, kind, data, x, y, by, groupby, row, col,
data, x, y, by_new, groupby_new = process_xarray(
data, x, y, by, groupby, use_dask, persist, gridded,
label, value_label, other_dims, kind=kind)
if kind == 'rgb' and robust:
# adapted from xarray
# https://github.com/pydata/xarray/blob/6af547cdd9beac3b18420ccb204f801603e11519/xarray/plot/utils.py#L729
vmax = np.nanpercentile(data[z], 100 - 2)
vmin = np.nanpercentile(data[z], 2)
# Scale interval [vmin .. vmax] to [0 .. 1], with darray as 64-bit float
# to avoid precision loss, integer over/underflow, etc with extreme inputs.
# After scaling, downcast to 32-bit float. This substantially reduces
# memory usage
data[z] = (
((data[z].astype("f8") - vmin) / (vmax - vmin))
).astype("f4")
data[z] = np.minimum(np.maximum(data[z], 0), 1)

if kind not in self._stats_types:
if by is None: by = by_new
Expand Down
8 changes: 8 additions & 0 deletions hvplot/tests/testgridplots.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def test_rgb_dataset_explicit_z(self):
rgb = self.da_rgb.to_dataset(name='z').hvplot.rgb(z='z')
self.assertEqual(rgb, RGB(([0, 1], [0, 1])+tuple(self.da_rgb.values)))

def test_rgb_dataset_robust(self):
rgb = self.da_rgb.to_dataset(name='z').hvplot.rgb(robust=True)
self.assertNotEqual(rgb, RGB(([0, 1], [0, 1])+tuple(self.da_rgb.values)))

def test_rgb_dataarray_groupby_explicit(self):
rgb = self.da_rgb_by_time.hvplot.rgb('x', 'y', groupby='time')
self.assertEqual(rgb[0], RGB(([0, 1], [0, 1])+tuple(self.da_rgb_by_time.values[0])))
Expand All @@ -93,6 +97,10 @@ def test_img_dataarray_infers_correct_other_dims(self):
img = self.da_img_by_time[0].hvplot()
self.assertEqual(img, Image(self.da_img_by_time[0], ['lon', 'lat'], ['value']))

def test_img_dataarray_robust_to_clim_percentile(self):
img = self.da_img_by_time[0].hvplot(robust=True)
assert img.opts["clim_percentile"] is True

def test_img_dataarray_groupby_infers_correct_other_dims(self):
img = self.da_img_by_time.hvplot(groupby='time')
self.assertEqual(img[0], Image(self.da_img_by_time[0], ['lon', 'lat'], ['value']))
Expand Down
Loading