Skip to content

Commit

Permalink
Add robust keyword (#1291)
Browse files Browse the repository at this point in the history
* Add robust

* Add tests and fix z

* Add docs

* Linting

* Remove print

* Update hvplot/converter.py

Co-authored-by: Maxime Liquet <[email protected]>

* Move robust

* Address comments

* Fix test

---------

Co-authored-by: Maxime Liquet <[email protected]>
  • Loading branch information
ahuang11 and maximlt authored Mar 27, 2024
1 parent 14e68a6 commit c5a1d3b
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 4 deletions.
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

0 comments on commit c5a1d3b

Please sign in to comment.