The rangeslider seems to be broken
kmdalton opened this issue · 2 comments
Bug report
I tried creating a rangeslider under ipyplot.imshow
by passing a keyword argument ("r", 1., 99., 99)
. When I actuate the slider, I get a complicated traceback and nothing happens. Here's a simple example to reproduce. I was trying to create a percentile based contrast slider.
from matplotlib import pyplot as plt
from mpl_interactions import ipyplot as iplt
import numpy as np
img = plt.imread("https://matplotlib.org/3.3.1/_images/stinkbug.png")
fig,ax = plt.subplots()
def f(prange):
pmin,pmax = prange
vmin = np.percentile(img, pmin)
vmax = np.percentile(img, pmax)
truncated = img.copy()
truncated = np.maximum(vmin, truncated)
truncated = np.minimum(vmax, truncated)
return truncated
controls = iplt.imshow(f, prange=("r", 1., 99., 99))
plt.show()
I get a traceback like this when I move the sliders:
Traceback (most recent call last):
File "anaconda/envs/whatever/lib/python3.8/site-packages/matplotlib/cbook/__init__.py", line 224, in process
func(*args, **kwargs)
File "anaconda/envs/whatever/lib/python3.8/site-packages/mpl_interactions/helpers.py", line 394, in changeify
update({"new": val})
File "anaconda/envs/whatever/lib/python3.8/site-packages/mpl_interactions/controller.py", line 182, in slider_updated
self._slider_updated(change, key, values)
File "anaconda/envs/whatever/lib/python3.8/site-packages/mpl_interactions/controller.py", line 156, in _slider_updated
self.params[key] = values[int(change["new"])]
TypeError: only size-1 arrays can be converted to Python scalars
Version Info
I saw this bug on 0.17.0
and 0.17.1
I think this would have been naturally fixed by #189 but definitely deserves a fix sooner than that will be finished.
This seems to be an issue only with matplotlib sliders as they don't use tuples
for their .val
attribute and instead use numpy arrays - which is my fault because I wrote them :(
This was introduced by #155 which added the int
casting for backwards compatibility with more numpy versions.
Fix incoming - thanks for the report!
FWIW you can achieve the same percentile thresholding while avoiding any .copy
operations like so:
fig, ax = plt.subplots()
def vmin(prange):
return np.percentile(img, prange[0])
def vmax(prange):
return np.percentile(img, prange[1])
controls = iplt.imshow(img, prange=("r", 1.0, 99.0, 99), vmin=vmin, vmax=vmax)
plt.show()
which I think should be more efficient as all you're doing is modifying the norm of the imshow instead of mucking around with the underlying data.
This also makes it clear how you might want to be able to pass vmin_vmax
as a single callable:
def vmin_vmax(prange):
return np.percentile(img, prange)
and then do iplt.imshow(img, prange(1,99,99), vmin_vmax=vmin_vmax)
I opened #196 about this