Make ij.py.show better
As discussed on the Image.sc Forum, we want ij.py.show to do a reasonable thing as conveniently as possible, while also supporting passing in hints for how best to render. Considerations:
-
No extra dependencies. We can use
matplotliborpillowor whatever to do the rendering, but we don't want to require any of those as hard dependencies because not everyone wants to use theshowfunction. It would be nice to have a reasonable series of fallbacks for default behavior that checks whether various supported libraries are available. -
Ability to pass arguments stating A) which renderer to preferentially use if available, and B) optionally, "hints" parameters for that renderer. We can use
kwargs, with each supported viz backend looking for its own key/value argument pairs.
Adding a couple of notes that came up from the forum discussion.
- When matplotlib is used, image size within Jupyter can be adjusted like:
matplotlib.rcParams['figure.figsize'] = [8.0, 6.0]
But this is a global setting, so it might not make sense to incorporate into ij.py.show. 2. ipywidgets can be used to provide ImageJ-like sliders inside Jupyter, but this is a bit clunky (at least how I have done it in the past): e.g.,
def f(z, color):
matplotlib.pyplot.imshow(image[:,:,z,color], cmap='gray')
ipywidgets.interact(
f,
z=ipywidgets.IntSlider(max=len(image[0,0,:,0])-1),
color=ipywidgets.Dropdown(options={'red': 0, 'green': 1, 'combined':(0, 1, 2)}));
I'll continue to investigate/think about both points.
@mellertd I pushed some changes to the ImageJ Python tutorial notebook that illustrate how to do dynamic visualization of N-dimensional ImgLib2 images; see imagej/tutorials@07711469bc0bf729401a59dce5088fac7840e714.

The relevant pieces of code are as follows:
A function for slicing out an ImgLib2 image plane as a 2D numpy array
def plane(image, pos):
while image.numDimensions() > 2:
image = ij.op().transform().hyperSliceView(image, image.numDimensions() - 1, pos[-1])
pos.pop()
return ij.py.from_java(image)
Note that the ij.py.from_java(image) call makes a copy of the plane from Java to Python.
A function for extracting non-planar axes as a dict
from jnius import autoclass, cast
CalibratedAxis = autoclass('net.imagej.axis.CalibratedAxis')
def axes(dataset):
axes = {}
for d in range(2, dataset.numDimensions()):
axis = cast(CalibratedAxis, dataset.axis(d))
label = axis.type().getLabel()
length = dataset.dimension(d)
axes[label] = length
return axes
The actual visualization using interact
import ipywidgets, matplotlib
widgets = {}
for label, length in axes(big_data).items():
widgets[label] = ipywidgets.IntSlider(description=label, max=length-1)
def f(**kwargs):
matplotlib.pyplot.imshow(plane(big_data, list(kwargs.values())), cmap='gray')
ipywidgets.interact(f, **widgets);
Probably the Python-fu could be stronger (e.g. dictionary comprehensions?). And probably some of this logic should make its way into pyimagej somewhere.
One problem with this approach seems to be that Jupyter wants to apply all slider changes sequentially, with no short circuiting. So as you drag the sliders, the display falls behind as it renders every single intermediate slider state. Hopefully someone who knows ipywidgets better can comment on how to avoid that.
One problem with this approach seems to be that Jupyter wants to apply all slider changes sequentially, with no short circuiting. So as you drag the sliders, the display falls behind as it renders every single intermediate slider state. Hopefully someone who knows
ipywidgetsbetter can comment on how to avoid that.
You can just use the option continuous_update = False in the ipywidgets.IntSlider() call so that image update only occurs upon widget release.