Skip to content

Commit

Permalink
Fix and improvements to ImageStack (#5961)
Browse files Browse the repository at this point in the history
  • Loading branch information
hoxbro authored Nov 4, 2023
1 parent 82dd07f commit 6c9f359
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 135 deletions.
2 changes: 1 addition & 1 deletion examples/reference/elements/bokeh/ImageStack.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
" <dt>Title</dt> <dd> ImageStack Element</dd>\n",
" <dt>Dependencies</dt> <dd>Bokeh</dd>\n",
" <dt>Backends</dt>\n",
" <dd><a href='./ImageStack.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='../bokeh/ImageStack.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='../matplotlib/ImageStack.ipynb'>Matplotlib</a></dd>\n",
" <dd><a href='../plotly/ImageStack.ipynb'>Plotly</a></dd>\n",
"</dl>\n",
Expand Down
2 changes: 1 addition & 1 deletion examples/reference/elements/matplotlib/ImageStack.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
" <dt>Title</dt> <dd> ImageStack Element</dd>\n",
" <dt>Dependencies</dt> <dd>Bokeh</dd>\n",
" <dt>Backends</dt>\n",
" <dd><a href='./ImageStack.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='../bokeh/ImageStack.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='../matplotlib/ImageStack.ipynb'>Matplotlib</a></dd>\n",
" <dd><a href='../plotly/ImageStack.ipynb'>Plotly</a></dd>\n",
"</dl>\n",
Expand Down
2 changes: 1 addition & 1 deletion examples/reference/elements/plotly/ImageStack.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
" <dt>Title</dt> <dd> ImageStack Element</dd>\n",
" <dt>Dependencies</dt> <dd>Bokeh</dd>\n",
" <dt>Backends</dt>\n",
" <dd><a href='./ImageStack.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='../bokeh/ImageStack.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='../matplotlib/ImageStack.ipynb'>Matplotlib</a></dd>\n",
" <dd><a href='../plotly/ImageStack.ipynb'>Plotly</a></dd>\n",
"</dl>\n",
Expand Down
57 changes: 41 additions & 16 deletions holoviews/element/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,16 +508,26 @@ def _coord2matrix(self, coord):

class ImageStack(Image):
"""
Supports the same constructor RGB and HSV elements, but without the
limit of 3/4 channels (one of more channels).
If only one channel it should behave the same as an Image.
Type of data inputs:
- 3D ndarray (x,y,level)
- A list of 2D ndarrays
- A dict of 2D ndarrays (key=level: value=2D ndarray)
- xarray with all the whistles
ImageStack expands the capabilities of Image to by supporting
multiple layers of images.
As there is many ways to represent multiple layers of images,
the following options are supported:
1) A 3D Numpy array with the shape (y, x, level)
2) A list of 2D Numpy arrays with identical shape (y, x)
3) A dictionary where the keys will be set as the vdims and the
values are 2D Numpy arrays with identical shapes (y, x).
If the dictionary's keys matches the kdims of the element,
they need to be 1D arrays.
4) A tuple containing (x, y, level_0, level_1, ...),
where the level is a 2D Numpy array in the shape of (y, x).
5) An xarray DataArray or Dataset where its `coords` contain the kdims.
If no kdims are supplied, x and y are used.
If no vdims are supplied, and the naming can be inferred like with a dictionary
the levels will be named level_0, level_1, etc.
"""

vdims = param.List(doc="""
Expand All @@ -530,20 +540,35 @@ class ImageStack(Image):
_vdim_reductions = {1: Image}

def __init__(self, data, kdims=None, vdims=None, **params):
if isinstance(data, np.ndarray) and data.ndim == 3:
x = np.arange(data.shape[0])
y = np.arange(data.shape[1])
data = (x, y, *(data[:, :, n] for n in range(data.shape[2])))
_kdims = kdims or self.kdims
if isinstance(data, list) and len(data):
x = np.arange(data[0].shape[1])
y = np.arange(data[0].shape[0])
data = (x, y, *data)
elif isinstance(data, dict):
first = next(v for k, v in data.items() if k not in _kdims)
xdim, ydim = map(str, _kdims)
if xdim not in data:
data[xdim] = np.arange(first.shape[1])
if ydim not in data:
data[ydim] = np.arange(first.shape[0])
elif isinstance(data, np.ndarray) and data.ndim == 3:
x = np.arange(data.shape[1])
y = np.arange(data.shape[0])
arr = (data[:, :, n] for n in range(data.shape[2]))
data = (x, y, *arr)
elif (
isinstance(data, tuple) and len(data) == 3
and isinstance(data[2], np.ndarray) and data[2].ndim == 3
):
data = (data[0], data[1], *(data[2][:, :,n] for n in range(data[2].shape[2])))
arr = (data[2][:, :, n] for n in range(data[2].shape[2]))
data = (data[0], data[1], *arr)

if vdims is None:
if isinstance(data, tuple):
vdims = [Dimension(f"level_{i}") for i in range(len(data[2:]))]
elif isinstance(data, dict):
vdims = [Dimension(key) for key in data.keys() if key not in self.kdims]
vdims = [Dimension(key) for key in data.keys() if key not in _kdims]
super().__init__(data, kdims=kdims, vdims=vdims, **params)


Expand Down
Loading

0 comments on commit 6c9f359

Please sign in to comment.