GithubHelp home page GithubHelp logo

haesleinhuepf / stackview Goto Github PK

View Code? Open in Web Editor NEW
105.0 105.0 5.0 86.86 MB

Interactive image stack viewing in jupyter notebooks based on ipycanvas and ipywidgets

License: BSD 3-Clause "New" or "Revised" License

Python 100.00%

stackview's Introduction

stackview ๐ŸงŠ๐Ÿ‘€

Interactive image stack viewing in jupyter notebooks based on ipycanvas and ipywidgets. TL;DR:

stackview.curtain(image, labels, continuous_update=True)

Installation

stackview can be installed using conda or pip.

conda install -c conda-forge stackview

OR

pip install stackview

If you run the installation from within a notebook, you need to restart Jupyter (not just the kernel), before you can use stackview.

Usage

You can use stackview from within jupyter notebooks as shown below. Also check out the demo in Binder

There is also a notebook demonstrating how to use stackview in Google Colab.

More example notebooks can be found in this folder.

Starting point is a 3D image dataset provided as numpy array.

from skimage.io import imread
image = imread('data/Haase_MRT_tfl3d1.tif', plugin='tifffile')

Slice view

You can then view it slice-by-slice:

import stackview
stackview.slice(image, continuous_update=True)

Static insight views

The insight function turns a numpy-array into a numpy-compatible array that has an image-display in jupyter notebooks.

insight(image[60])

img.png

Images of 32-bit and 64-bit type integer are displayed as labels.

blobs = imread('data/blobs.tif')
labels = label(blobs > 120)

insight(labels)

img.png

Annotate regions

To create label images interactively, e.g. for machine learning training, the stackview.annotate function offers basic label drawing tools. Click and drag for drawing. Hold the ALT key for erasing. Annotations are drawn into a labels image you need to create before drawing.

import numpy as np
labels = np.zeros(image.shape).astype(np.uint32)

stackview.annotate(image, labels)

img.png

Note: In case the interface is slow, consider using smaller images, e.g. by cropping or resampling.

Pick intensities

To read the intensity of pixels where the mouse is moving, use the picker.

stackview.picker(image, continuous_update=True)

Orthogonal view

Orthogonal views are also available:

stackview.orthogonal(image, continuous_update=True)

Curtain

Furthermore, to visualize an original image in combination with a processed version, a curtain view may be helpful:

stackview.curtain(image, modified_image * 65537, continuous_update=True)

The curtain also works with 2D data. Btw. to visualize both images properly, you need adjust their grey value range yourself. For example, multiply a binary image with 255 so that it visualizes nicely side-by-side with the original image in 8-bit range:

binary = (slice_image > threshold_otsu(slice_image)) * 255
stackview.curtain(slice_image, binary, continuous_update=True)

The same also works with label images

from skimage.measure import label
labels = label(binary)
stackview.curtain(slice_image, labels, continuous_update=True)

Side-by-side view

A side-by-side view for colocalization visualization is also available. If you're working with time-lapse data, you can also use this view for visualizing differences between timepoints:

stackview.side_by_side(image_stack[1:], image_stack[:-1], continuous_update=True, display_width=300)

Switch

The switch function allows to switch between a list or dictionary of images.

stackview.switch([
    slice_image,
    binary,
    labels
])

Switch toggleable

You can also view multiple channels with different colormaps at the same time using the toggleable parameter of switch. It is recommended to also pass a list of colormaps. Colormap names can be taken from Matplotlib and stackview aims at compatibility with microfilm.

hela_cells = imread("data/hela-cells.tif")

stackview.switch(
    {"lysosomes":   hela_cells[:,:,0],
     "mitochondria":hela_cells[:,:,1],
     "nuclei":      hela_cells[:,:,2]
    },
    colormap=["pure_magenta", "pure_green", "pure_blue"],
    toggleable=True
)

Crop

You can crop images interactively:

crop_widget = stackview.crop(image_stack, continuous_update=True)
crop_widget

... and retrieve the crop range as a tuple of slice objects:

r = crop_widget.range
r

Output:

(slice(0, 40, 1), slice(40, 80, 1), slice(80, 120, 1))

... or you can crop the image directly:

cropped_image = crop_widget.crop()
cropped_image.shape

Output:

(40, 40, 40)

Interact

Exploration of the parameter space of image processing functions is available using interact:

from skimage.filters.rank import maximum
stackview.interact(maximum, slice_image)

This might be useful for custom functions implementing image processing workflows:

from skimage.filters import gaussian, threshold_otsu, sobel
def my_custom_code(image, sigma:float = 1, show_labels: bool = True):
    sigma = abs(sigma)
    blurred_image = gaussian(image, sigma=sigma)
    binary_image = blurred_image > threshold_otsu(blurred_image)
    edge_image = sobel(binary_image)
    
    if show_labels:
        return label(binary_image)
    else:
        return edge_image * 255 + image 

stackview.interact(my_custom_code, slice_image)

If you want to use a pulldown for selecting input image(s), you need to pass a dictionary of (name, image) pairs as context, e.g. context=globals():

image1 = imread("data/Haase_MRT_tfl3d1.tif")
image2 = image1[:,:,::-1]

stackview.interact(gaussian, context=globals(), continuous_update=True)

To add an insight-view automatically to results of functions, you can add this.

@jupyter_displayable_output
def my_gaussian(image, sigma):
    return gaussian(image, sigma)

my_gaussian(image[60], 2)

img.png

Assist

The stackview.assist() function can guide you through all imported (and supported) image processing functions. Note: The interface may be slow or crash if you have many functions imported. Consider using it in an empty notebook with only functions or library imported that might be relevant for the taks.

stackview.assist(context=globals(), continuous_update=True)

img.png

Contributing

Contributions, bug-reports and ideas for further development are very welcome.

License

Distributed under the terms of the BSD-3 license, "stackview" is free and open source software

Issues

If you encounter any problems, please create a thread on image.sc along with a detailed description and tag @haesleinhuepf.

See also

There are other libraries doing similar stuff

stackview's People

Contributors

haesleinhuepf avatar jookuma avatar orena1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

stackview's Issues

slice slider is too wide

The slice-slicer is sometimes so wide that a scroll bar is introduced:
image

Would be good if it adapted to available space

Add colormap to stackview.slice

Hi @haesleinhuepf,

It would be cool to be able to pass a colormap to the stackview.slice function.
I like for example the sequential colormaps from matplotlib 'magma' and 'viridis', maybe you could also put one of these as default colormap?

Best,
Mara

Error displaying widget: model not found

Hi

I tried to run it on our university jupyter lab via ondemands,
I installed a conda environement and stackivew via conda install -c conda-forge stackview and ran the following example

import stackview

import numpy as np
from skimage.io import imread
from skimage.filters import gaussian

image = imread('https://github.com/haesleinhuepf/stackview/blob/main/docs/data/Haase_MRT_tfl3d1.tif?raw=true', plugin='tifffile')
stackview.slice(image, continuous_update=True)

which give me the following error message (with no extra information)

Error displaying widget: model not found

Is there something else that need to be installed ? Would you have any tips to identify the issue?

Note that it works fine locally for me via vscode

stackview.switch fails with image stack

I have a stack of images and a stack of corresponding labels that I want to overlay. stackview.switch works great for a single slice like:

stackview.switch(
    [image_stack[0], labels[0]],
    colormap=["viridis", "pure_red"],
    toggleable=True,
)

However, I'd like to have a slice slider to scroll through all the slices. This seems like it should be possible from the documentation, but when I try:

stackview.switch(
    [image_stack, labels],
    colormap=["viridis", "pure_red"],
    toggleable=True,
)

I get:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[23], line 1
----> 1 stackview.switch(
      2     [image_stack, labels],
      3     colormap=["viridis", "pure_red"],
      4     toggleable=True,
      5 )

File /opt/conda/lib/python3.10/site-packages/stackview/_switch.py:129, in switch(images, slice_number, axis, display_width, display_height, continuous_update, slider_text, zoom_factor, zoom_spline_order, colormap, display_min, display_max, toggleable)
    127     buttons.append(button)
    128 if toggleable:
--> 129     display_function()
    130 return ipywidgets.VBox([_no_resize(view), ipywidgets.HBox(buttons), slice_slider])

File /opt/conda/lib/python3.10/site-packages/stackview/_switch.py:101, in switch.<locals>.display_(buttons, images, colormap, display_min, display_max)
     99             display_image = display_image_to_add
    100         else:
--> 101             display_image = display_image + display_image_to_add
    103 display_image = np.minimum(display_image, np.asarray([[255]]))
    105 viewer.view.colormap = None

ValueError: operands could not be broadcast together with shapes (50,200,3) (50,200,3,200) 

TypeError: Invalid shape () for image data when calling max() on stackview._static_view.StackViewNDArray

The following code is based on 09_gauss_otsu_labeling.ipynb from https://github.com/BiAPoL/Bio-image_Analysis_with_Python/

from skimage.io import imread
import napari_segment_blobs_and_things_with_membranes as nsbatwm

input_image = imread("data/BBBC022/IXMtest_A02_s9.tif")[:,:,0][0:200, 200:400]

blurred_image = nsbatwm.gaussian_blur(input_image, sigma=3)
blurred_image.max()

Output:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File ~/mambaforge/envs/my_first_env/lib/python3.9/site-packages/IPython/core/formatters.py:344, in BaseFormatter.__call__(self, obj)
    342     method = get_real_method(obj, self.print_method)
    343     if method is not None:
--> 344         return method()
    345     return None
    346 else:

File ~/mambaforge/envs/my_first_env/lib/python3.9/site-packages/stackview/_static_view.py:63, in StackViewNDArray._repr_html_(self)
     60 labels = _is_label_image(self)
     62 import matplotlib.pyplot as plt
---> 63 _imshow(self,
     64         labels=labels,
     65         continue_drawing=True,
     66         colorbar=not labels)
     67 image = _png_to_html(_plt_to_png())
     69 if size_in_bytes > 1024:

File ~/mambaforge/envs/my_first_env/lib/python3.9/site-packages/stackview/_static_view.py:225, in _imshow(image, title, labels, min_display_intensity, max_display_intensity, plot, colorbar, colormap, alpha, continue_drawing)
    223 if plot is None:
    224     import matplotlib.pyplot as plt
--> 225     plt.imshow(image, cmap=cmap, vmin=min_display_intensity, vmax=max_display_intensity,
    226                interpolation='nearest', alpha=alpha)
    227     if colorbar:
    228         plt.colorbar()

File ~/mambaforge/envs/my_first_env/lib/python3.9/site-packages/matplotlib/pyplot.py:2695, in imshow(X, cmap, norm, aspect, interpolation, alpha, vmin, vmax, origin, extent, interpolation_stage, filternorm, filterrad, resample, url, data, **kwargs)
   2689 @_copy_docstring_and_deprecators(Axes.imshow)
   2690 def imshow(
   2691         X, cmap=None, norm=None, *, aspect=None, interpolation=None,
   2692         alpha=None, vmin=None, vmax=None, origin=None, extent=None,
   2693         interpolation_stage=None, filternorm=True, filterrad=4.0,
   2694         resample=None, url=None, data=None, **kwargs):
-> 2695     __ret = gca().imshow(
   2696         X, cmap=cmap, norm=norm, aspect=aspect,
   2697         interpolation=interpolation, alpha=alpha, vmin=vmin,
   2698         vmax=vmax, origin=origin, extent=extent,
   2699         interpolation_stage=interpolation_stage,
   2700         filternorm=filternorm, filterrad=filterrad, resample=resample,
   2701         url=url, **({"data": data} if data is not None else {}),
   2702         **kwargs)
   2703     sci(__ret)
   2704     return __ret

File ~/mambaforge/envs/my_first_env/lib/python3.9/site-packages/matplotlib/__init__.py:1442, in _preprocess_data.<locals>.inner(ax, data, *args, **kwargs)
   1439 @functools.wraps(func)
   1440 def inner(ax, *args, data=None, **kwargs):
   1441     if data is None:
-> 1442         return func(ax, *map(sanitize_sequence, args), **kwargs)
   1444     bound = new_sig.bind(ax, *args, **kwargs)
   1445     auto_label = (bound.arguments.get(label_namer)
   1446                   or bound.kwargs.get(label_namer))

File ~/mambaforge/envs/my_first_env/lib/python3.9/site-packages/matplotlib/axes/_axes.py:5665, in Axes.imshow(self, X, cmap, norm, aspect, interpolation, alpha, vmin, vmax, origin, extent, interpolation_stage, filternorm, filterrad, resample, url, **kwargs)
   5657 self.set_aspect(aspect)
   5658 im = mimage.AxesImage(self, cmap=cmap, norm=norm,
   5659                       interpolation=interpolation, origin=origin,
   5660                       extent=extent, filternorm=filternorm,
   5661                       filterrad=filterrad, resample=resample,
   5662                       interpolation_stage=interpolation_stage,
   5663                       **kwargs)
-> 5665 im.set_data(X)
   5666 im.set_alpha(alpha)
   5667 if im.get_clip_path() is None:
   5668     # image does not already have clipping set, clip to axes patch

File ~/mambaforge/envs/my_first_env/lib/python3.9/site-packages/matplotlib/image.py:710, in _ImageBase.set_data(self, A)
    706     self._A = self._A[:, :, 0]
    708 if not (self._A.ndim == 2
    709         or self._A.ndim == 3 and self._A.shape[-1] in [3, 4]):
--> 710     raise TypeError("Invalid shape {} for image data"
    711                     .format(self._A.shape))
    713 if self._A.ndim == 3:
    714     # If the input data has values outside the valid range (after
    715     # normalisation), we issue a warning and then clip X to the bounds
    716     # - otherwise casting wraps extreme values, hiding outliers and
    717     # making reliable interpretation impossible.
    718     high = 255 if np.issubdtype(self._A.dtype, np.integer) else 1

TypeError: Invalid shape () for image data

assist crashes with JavaScript error

In some hard-to-reproduce scenarios, stackview.assist(context=globals()) crashes with a JavaScript error. In case you observe such issues, please report which libraries and functions you had imported. This error presumably only happens with specific modules / functions listed in globals().

interact has issues when passing a Mesh instead of image

When using stackview.interact with something that is no image, it crashes:

import numpy as np
import stackview

surface = nppas._vedo_stanford_bunny()
mesh = nppas.to_vedo_mesh(surface)

def do_show(mesh, zoom:float = np.exp(1), azimuth:float = 0, elevation:float = 0, roll:float = 0):
    from vedo import Plotter
    plt = Plotter(offscreen=True, size=[600,400], N=2)
    plt.show(mesh, zoom=np.log(zoom), azimuth=azimuth, elevation=elevation, roll=roll)
    return plt.screenshot(asarray=True)

stackview.interact(do_show, mesh, continuous_update=True)

Error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[5], line 10
      7     plt.show(mesh, zoom=np.log(zoom), azimuth=azimuth, elevation=elevation, roll=roll)
      8     return plt.screenshot(asarray=True)
---> 10 stackview.interact(do_show, mesh, continuous_update=True)

File ~\mambaforge\envs\bio39\lib\site-packages\stackview\_interact.py:108, in interact(func, image, continuous_update, context, zoom_factor, zoom_spline_order, viewer, *args, **kwargs)
    106 viewer_was_none = viewer is None
    107 if viewer_was_none:
--> 108     viewer = _SliceViewer(image, zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order)
    109 viewer.slice_slider.continuous_update=continuous_update
    110 command_label = ipywidgets.Label(value=func_name + "()")

File ~\mambaforge\envs\bio39\lib\site-packages\stackview\_slice_viewer.py:21, in _SliceViewer.__init__(self, image, slice_number, axis, display_width, display_height, continuous_update, slider_text, zoom_factor, zoom_spline_order)
     18 self.image = image
     20 if slice_number is None:
---> 21     slice_number = int(image.shape[axis] / 2)
     23 if len(self.image.shape) == 3 and self.image.shape[-1] != 3:
     24     self.view = ImageWidget(np.take(image, slice_number, axis=axis), zoom_factor=zoom_factor, zoom_spline_order=zoom_spline_order)

AttributeError: 'Mesh' object has no attribute 'shape'

Feature request: crop?

Any chance I can convince you to add sliders to crop the dataset in stackview.slice and also get those values?

image

Zoom

It would be nice to show the image with a specified zoom factor. Proposed by @paxcalpt

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.