From 36cf0de621013639cd1935c436a878c9fc1aade0 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 09:48:17 +0200 Subject: [PATCH 1/8] Drop z dimension from ImageStack DataArray before shading --- holoviews/operation/datashader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index b24d28c7fd..c3e283660c 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -1291,7 +1291,7 @@ def _process(self, element, key=None): vdim = element.vdims array = element.data if hasattr(array, "to_array"): - array = array.to_array("z") + array = array.to_array("z").isel(z=0) array = array.transpose(*[kdim.name for kdim in kdims], ...) else: vdim = element.vdims[0].name From bbcd6342cce7f7f7437d7a2d0a4d18ea5e6198a1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 11:37:47 +0200 Subject: [PATCH 2/8] Fix handling of different array types --- holoviews/operation/datashader.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index c3e283660c..55ce09efc7 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -1289,10 +1289,23 @@ def _process(self, element, key=None): kdims = element.kdims if isinstance(element, ImageStack): vdim = element.vdims + if element.interface.datatype != 'xarray': + element = element.clone(datatype=['xarray']) array = element.data - if hasattr(array, "to_array"): - array = array.to_array("z").isel(z=0) - array = array.transpose(*[kdim.name for kdim in kdims], ...) + # If data is a Dataset it has to be converted to a + # DataArray, either by selecting the singular value + # dimension or by adding a z-dimension + kdims = [kdim.name for kdim in kdims] + if not element.interface.packed(element): + if len(vdim) == 1: + array = array[vdim[0].name] + else: + array = array.to_array("z") + # If data is 3D then we have one extra constant dimension + if array.ndim > 3: + drop = [d for d in array.dims if d not in kdims+["z"]] + array = array.squeeze(drop) + array = array.transpose(*kdims, ...) else: vdim = element.vdims[0].name array = element.data[vdim] @@ -1335,7 +1348,7 @@ def _process(self, element, key=None): if self.p.clims: shade_opts['span'] = self.p.clims elif ds_version > Version('0.5.0') and self.p.cnorm != 'eq_hist': - shade_opts['span'] = element.range(vdim) + shade_opts['span'] = (array.min(), array.max()) params = dict(get_param_values(element), kdims=kdims, bounds=bounds, vdims=RGB.vdims[:], From 9e65c7633e23da8ceae940374d7e37448dd60fbc Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 11:38:09 +0200 Subject: [PATCH 3/8] Allow DataArray even for single value dimension --- holoviews/core/data/xarray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/data/xarray.py b/holoviews/core/data/xarray.py index 5e7cef72c7..62139b949e 100644 --- a/holoviews/core/data/xarray.py +++ b/holoviews/core/data/xarray.py @@ -93,7 +93,7 @@ def retrieve_unit_and_label(dim): if isinstance(data, xr.DataArray): kdim_len = len(kdim_param.default) if kdims is None else len(kdims) vdim_len = len(vdim_param.default) if vdims is None else len(vdims) - if vdim_len > 1 and kdim_len == len(data.dims)-1 and data.shape[-1] == vdim_len: + if kdim_len == len(data.dims)-1 and data.shape[-1] == vdim_len: packed = True elif vdims: vdim = vdims[0] From 3481244b5aab817c752ff20d04ce3b324ef07f3b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 11:58:09 +0200 Subject: [PATCH 4/8] Squeeze packed DataArray --- holoviews/core/data/xarray.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/holoviews/core/data/xarray.py b/holoviews/core/data/xarray.py index 62139b949e..9da2170c80 100644 --- a/holoviews/core/data/xarray.py +++ b/holoviews/core/data/xarray.py @@ -446,7 +446,11 @@ def unpack_scalar(cls, dataset, data): Given a dataset object and data in the appropriate format for the interface, return a simple scalar. """ - if not cls.packed(dataset) and len(data.data_vars) == 1: + if cls.packed(dataset): + array = data.squeeze() + if len(array.shape) == 0: + return array.item() + elif len(data.data_vars) == 1: array = data[dataset.vdims[0].name].squeeze() if len(array.shape) == 0: return array.item() From d0e088c67200b6a070b9ea93b1669afe05ae0a9b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 12:12:29 +0200 Subject: [PATCH 5/8] Fix span calc --- holoviews/operation/datashader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index 55ce09efc7..d907a9d217 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -1348,7 +1348,7 @@ def _process(self, element, key=None): if self.p.clims: shade_opts['span'] = self.p.clims elif ds_version > Version('0.5.0') and self.p.cnorm != 'eq_hist': - shade_opts['span'] = (array.min(), array.max()) + shade_opts['span'] = (array.min().item(), array.max().item()) params = dict(get_param_values(element), kdims=kdims, bounds=bounds, vdims=RGB.vdims[:], From 755e32f096c9b67599d0f7ae8b9f0f460228a7b7 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 12:26:25 +0200 Subject: [PATCH 6/8] Fix span calc and handle dask --- holoviews/operation/datashader.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index d907a9d217..6ece208729 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -1286,11 +1286,13 @@ def _process(self, element, key=None): ydensity = element.ydensity bounds = element.bounds + # Convert to xarray if not already + if element.interface.datatype != 'xarray': + element = element.clone(datatype=['xarray']) + kdims = element.kdims if isinstance(element, ImageStack): vdim = element.vdims - if element.interface.datatype != 'xarray': - element = element.clone(datatype=['xarray']) array = element.data # If data is a Dataset it has to be converted to a # DataArray, either by selecting the singular value @@ -1310,6 +1312,9 @@ def _process(self, element, key=None): vdim = element.vdims[0].name array = element.data[vdim] + # Dask is not supported by shade so materialize it + array = array.compute() + shade_opts = dict( how=self.p.cnorm, min_alpha=self.p.min_alpha, alpha=self.p.alpha ) From 67bcbcc340b0a7b03570ef110cc1a91b8b53f796 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 12:36:27 +0200 Subject: [PATCH 7/8] Add test --- holoviews/tests/operation/test_datashader.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/holoviews/tests/operation/test_datashader.py b/holoviews/tests/operation/test_datashader.py index 5feb8906cb..235eb05f43 100644 --- a/holoviews/tests/operation/test_datashader.py +++ b/holoviews/tests/operation/test_datashader.py @@ -827,6 +827,22 @@ def test_aggregate_points_categorical(self): actual = img.data assert (expected.data.to_array("z").values == actual.T.values).all() + def test_aggregate_points_categorical_one_category(self): + points = Points([(0.2, 0.3, 'A'), (0.4, 0.7, 'A'), (0, 0.99, 'A')], vdims='z') + img = aggregate(points, dynamic=False, x_range=(0, 1), y_range=(0, 1), + width=2, height=2, aggregator=ds.by('z', ds.count())) + x = np.array([0.25, 0.75]) + y = np.array([0.25, 0.75]) + a = np.array([[1, 2], [0, 0]]) + xrds = xr.DataArray( + a, + dims=('x', 'y'), + coords={"x": x, "y": y} + ) + expected = ImageStack(xrds, kdims=["x", "y"], vdims=["a"]) + actual = img.data + assert (expected.data.to_array("z").values == actual.T.values).all() + def test_aggregate_points_categorical_mean(self): points = Points([(0.2, 0.3, 'A', 0.1), (0.4, 0.7, 'B', 0.2), (0, 0.99, 'C', 0.3)], vdims=['cat', 'z']) img = aggregate(points, dynamic=False, x_range=(0, 1), y_range=(0, 1), From f10802088c3f6099b268a15b8046a2dc5ba9bd90 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 19 Sep 2024 13:55:23 +0200 Subject: [PATCH 8/8] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Høxbro Hansen --- holoviews/operation/datashader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index 6ece208729..a725520b98 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -1294,7 +1294,7 @@ def _process(self, element, key=None): if isinstance(element, ImageStack): vdim = element.vdims array = element.data - # If data is a Dataset it has to be converted to a + # If data is a xarray Dataset it has to be converted to a # DataArray, either by selecting the singular value # dimension or by adding a z-dimension kdims = [kdim.name for kdim in kdims] @@ -1306,7 +1306,7 @@ def _process(self, element, key=None): # If data is 3D then we have one extra constant dimension if array.ndim > 3: drop = [d for d in array.dims if d not in kdims+["z"]] - array = array.squeeze(drop) + array = array.squeeze(dim=drop) array = array.transpose(*kdims, ...) else: vdim = element.vdims[0].name