Skip to content

Ability to use discrete colormaps in mne.viz.plot_source_estimates without color interpolation #13190

Open
@caiw

Description

@caiw

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:

Image

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:

  1. Is there a specific reason, other than performance, why this approach is required?
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions