Description
Describe the new feature or enhancement
We are using mne to plot single-source points with a discrete colormap to show "which model fits best here" results. We have found that when we supply discrete colormaps, the plotted results end up with a little "halo" of false colour around the edges of of the vertices which should be coloured:
I believe the reason for this is that instead of referencing into the colormap directly to get the colors to plot, mne creates a LUT from 256 sample colours and then the renderer interpolates between them.
I acknowledge that in most cases, where continuous values are plotted using a continuous colormap, interpolated LUTs are a great solution, but I believe they are preventing us from controlling our colors precisely.
I had two questions:
- Is there a specific reason, other than performance, why this approach is required?
- If we worked on a PR which allowed a specific Colormap object to be queried to plot all colours (thereby allowing us to precisely control color output), would you consider it?
Describe your proposed implementation
My initial approach for the PR would be to create an object which behaves like the LUT externally, but which internally queries the supplied Colormap directly.
Describe possible alternatives
The only other option I can think of would be to increase the number of samples in the LUT to greater than 256. However this doesn't seem like it solves the issue "once and for all", unless the above proposed solution would be insufficiently performant.
Additional context
More concretely, in our specific example we have created a forced-discrete colormap like this:
class DiscreteListedColormap(ListedColormap):
"""Like ListedColormap, but without interpolation between values."""
def __init__(self, colors: list, name = 'from_list', N = None, scale01: bool = False):
"""
Args:
scale01 (bool): True if the values will be supplied to the colormap in the range [0, 1] instead of the range
[0, N-1].
"""
self.scale01: bool = scale01
super().__init__(colors=colors, name=name, N=N)
def __call__(self, X, *args, **kwargs):
if self.scale01:
# Values are supplied between 0 and 1, so map them up to their corresponding index (or close to it)
X *= self.N
rounded = np.round(X).astype(int)
return super().__call__(X=rounded, *args, **kwargs)
But then by sampling this colormap 256 times and interpolating the results when plotting, we lose our ability to control which precise values correspond to which precise colours.