Misleading BoundaryNorm error

Folks,

still in my exploring phase of Matplotlib's ecosystem I ran into following mismatch between the APIs of BoundaryNorm and Normalize.

See the following example:

import matplotlib as mpl
c = mpl.cm.get_cmap()
bnorm = mpl.colors.BoundaryNorm([0,1,2], c.N)
nnorm = mpl.colors.Normalize(0, 2)

# This works:
In [8]: c(nnorm(1.1))
Out[8]: (0.64199873497786197, 1.0, 0.32574320050600891, 1.0)

# This doesn't:
In [9]: c(bnorm(1.1))
(...)
TypeError: 'numpy.int16' object does not support item assignment

# But this works:
In [10]: c(bnorm([1.1]))
Out[10]: array([[ 0.5, 0. , 0. , 1. ]])

From the doc I would expect BoundaryNorm and Normalize to work the same way. I find the error sent by BoundaryNorm quite misleading.

Should I fill a bug report for this?

Thanks!

Fabien

Fabien,

What happens if your force the boundaries to floats? By that I mean:
bnorm = mpl.colors.BoundaryNorm([0.0, 1.0, 2.0], c.N)
-Paul

···

On Wed, Jul 29, 2015 at 3:18 AM, Fabien <fabien.maussion@...287...> wrote:

Folks,

still in my exploring phase of Matplotlib's ecosystem I ran into
following mismatch between the APIs of BoundaryNorm and Normalize.

See the following example:

import matplotlib as mpl
c = mpl.cm.get_cmap()
bnorm = mpl.colors.BoundaryNorm([0,1,2], c.N)
nnorm = mpl.colors.Normalize(0, 2)

# This works:
In [8]: c(nnorm(1.1))
Out[8]: (0.64199873497786197, 1.0, 0.32574320050600891, 1.0)

# This doesn't:
In [9]: c(bnorm(1.1))
(...)
TypeError: 'numpy.int16' object does not support item assignment

# But this works:
In [10]: c(bnorm([1.1]))
Out[10]: array([[ 0.5, 0. , 0. , 1. ]])

From the doc I would expect BoundaryNorm and Normalize to work the same
way. I find the error sent by BoundaryNorm quite misleading.

Should I fill a bug report for this?

Thanks Paul,

it doesn't change anything. The problem is related to the variable iret which is of shape (): the assignment fails at L1281 in colors.py. Here is the code:

     def __call__(self, x, clip=None):
         if clip is None:
             clip = self.clip
         x = ma.asarray(x) # <--- doesnt guarantee 1D
         mask = ma.getmaskarray(x)
         xx = x.filled(self.vmax + 1)
         if clip:
             np.clip(xx, self.vmin, self.vmax)
         iret = np.zeros(x.shape, dtype=np.int16) # <--- x.shape = ()
         for i, b in enumerate(self.boundaries):
             iret[xx >= b] = i
         if self._interp:
             scalefac = float(self.Ncmap - 1) / (self.N - 2)
             iret = (iret * scalefac).astype(np.int16)
         iret[xx < self.vmin] = -1 # <--- error
         iret[xx >= self.vmax] = self.Ncmap
         ret = ma.array(iret, mask=mask)
         if ret.shape == () and not mask:
             ret = int(ret)
         return ret

It should be easy to fix by changing
     iret = np.zeros(x.shape, dtype=np.int16)
to:
     iret = np.atleast1d(np.zeros(x.shape, dtype=np.int16))

But this would lead to an output which is never a scalar even if a scalar is given as input. Is that a problem?

Cheers,

Fabien

···

On 07/29/2015 10:34 PM, Paul Hobson wrote:

    See the following example:

    import matplotlib as mpl
    c = mpl.cm.get_cmap()
    bnorm = mpl.colors.BoundaryNorm([0,1,2], c.N)
    nnorm = mpl.colors.Normalize(0, 2)

    # This works:
    In [8]: c(nnorm(1.1))
    Out[8]: (0.64199873497786197, 1.0, 0.32574320050600891, 1.0)

    # This doesn't:
    In [9]: c(bnorm(1.1))
    (...)
    TypeError: 'numpy.int16' object does not support item assignment

    # But this works:
    In [10]: c(bnorm([1.1]))
    Out[10]: array([[ 0.5, 0. , 0. , 1. ]])

      From the doc I would expect BoundaryNorm and Normalize to work the
    same
    way. I find the error sent by BoundaryNorm quite misleading.

    Should I fill a bug report for this?

Fabien,

What happens if your force the boundaries to floats? By that I mean:
bnorm = mpl.colors.BoundaryNorm([0.0, 1.0, 2.0], c.N)
-Paul

Forcing the scalar to be a 1-element array would still leave the API
inconsistent with what you show for Normalize. One solution is to
flag a scalar at the start, and then de-reference at the end. Would
you like to submit a PR to take care of this?

Hi,

my very first PR here:
https://github.com/matplotlib/matplotlib/pull/4824

Thanks,

Fabien

···

On 07/30/2015 10:07 AM, Eric Firing wrote:

Forcing the scalar to be a 1-element array would still leave the API
inconsistent with what you show for Normalize. One solution is to
flag a scalar at the start, and then de-reference at the end. Would
you like to submit a PR to take care of this?

Thank you! We are always happy to have new contributors!