Show colorbar with values

Hi again :slight_smile:

In my application would be fine to show a colorbar with values like this figure

and additionally, put a string with units like “ms” or “V”, etc.

Is this feature available? or maybe it will be added in the future? If not, do you have any suggestion to carry out?

Thanks!

Hi @scaracciolo! Keep these coming, we love them! :blush:

We don’t natively have a colorbar, but, because matplotlib has a Qt backend, you can embed a matplotlib widget within napari. matplotlib also lets you I think that’s the fastest route for you to get this working. Here’s two examples of matploltib integration:

and one for which you don’t need example data (it gets downloaded):

This page from the matplotlib documentation has lots of links to examples using colorbar, so you should be able to find one to make exactly the one you need, then add_dock_widget as in the examples above.

1 Like

(For the second example, you do need to download the full repo, not just the .py file.)

FYI, there has been some discussion about adding support for colorbars later on (see here https://github.com/napari/napari/issues/1779, the conversation about a colorbar feature is mixed in with other discussion about colormaps).

So, nothing you can expect in the short term future (unless you’d like to work on that yourself :slight_smile:) but people are thinking about this already.

2 Likes

Thanks for reply @jni and @GenevieveBuckley! :slight_smile:

I followed the advice of @jni and I put colorbars via matplotlib.with Qt backend. This approach works but I observed that If the colorbar scale change with sliders, so I must use several times .draw_idle() to refresh the colorbar widget. This method reduces a bit of the performance of my application :sweat_smile:. So, I think matplotlib colorbar is very fine when static behavior is required but less useful in dynamic requirements. Maybe there is a better solution with matplotlib but I don’t know-how :thinking:

Anyway, the application is a bit slower but works fine yet :slight_smile:

Thanks!!

Good to hear that it’s working! Eventually we might add VisPy colorbars and so on, but it’s good to have something that works now. =)

Regarding matplotlib speed, if you can share the code, perhaps there is something we can improve there? I suspect you can change some attributes in the colorbar objects themselves rather than draw a new colorbar each time? Or, if you’re already doing that, well, I don’t know! :joy: But having code (with example/random data) makes it easier to have a discussion!

btw, does the call to draw_idle() block the UI? Or is it just that you would prefer the display to update more rapidly?

2 Likes

Hi @jni, thanks for your support!

I think would be a good idea to look at how to use Vispy colorbars because I use a custom colormap defined with Vispy due napari surface needs it a vispy.Color.Colormap, so when I want to show the colorbar, I have to create the same custom colormap but in Matplotlib. So, If I would use the Vispy colorbar, I think I can avoid duplicated custom colormaps in different APIs (and maybe better performance?).

Regarding Matplotlib speed, draw_idle() doesn’t block the UI, only the UI update is less smoothing.

I coded “minimal” example, with 3 colorbars like I have,

import napari
import matplotlib as mpl
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt

from nilearn import datasets
from nilearn import surface

import numpy as np

nki_dataset = datasets.fetch_surf_nki_enhanced(n_subjects=1)
fsaverage = datasets.fetch_surf_fsaverage()

brain_vertices, brain_faces = surface.load_surf_data(fsaverage['pial_left'])
timeseries = surface.load_surf_data(nki_dataset['func_left'][0])
timeseries = timeseries.transpose((1, 0))

def axis_changed(event):

    idx_max = viewer.dims.range[0][1]
    idx = viewer.dims.current_step[0]

    if idx < idx_max and layer.visible:
        random_number = np.random.random()
        mappable1.set_clim(random_number, random_number+1)
        mappable2.set_clim(random_number, random_number+1)
        mappable3.set_clim(random_number, random_number+1)
        # comment next line to check performance
        colorbars_widget.draw_idle()

with napari.gui_qt():

    viewer = napari.Viewer(ndisplay=3)

    with plt.style.context('dark_background'):

        colorbars_widget = FigureCanvas(Figure(constrained_layout=True))
        fig = colorbars_widget.figure

        gs = fig.add_gridspec(3, 1)
        ax1 = fig.add_subplot(gs[0, 0])
        ax2 = fig.add_subplot(gs[1, 0])
        ax3 = fig.add_subplot(gs[2, 0])

        cmap1 = mpl.cm.jet
        mappable1 = mpl.cm.ScalarMappable(cmap=cmap1)
        colorbar1 = fig.colorbar(mappable1, cax=ax1, orientation='horizontal')
        colorbar1.set_label('jet')

        cmap2 = mpl.cm.gist_rainbow
        mappable2 = mpl.cm.ScalarMappable(cmap=cmap2)
        colorbar2 = fig.colorbar(mappable2, cax=ax2, orientation='horizontal')
        colorbar2.set_label('gist_rainbow')

        cmap3 = mpl.cm.viridis
        mappable3 = mpl.cm.ScalarMappable(cmap=cmap3)
        colorbar3 = fig.colorbar(mappable3, cax=ax3, orientation='horizontal')
        colorbar3.set_label('viridis')
    

    layer = viewer.add_surface((brain_vertices, brain_faces, timeseries))
    layer.colormap = cmap1.name

    viewer.dims.events.axis.connect(axis_changed)

    colorbars_widget.setFixedWidth(300)
    colorbars_widget.setFixedHeight(200)
    
    colorbars_dock = viewer.window.add_dock_widget(colorbars_widget, name='Colorbars', area = 'left')

To check the performance please comment the line colorbars_widget.draw_idle().

If I use only one colorbar, speed is better, but in my case, three colorbars are required, besides, in my real application I have more surfaces and performance is a bit worse … maybe I need a better computer? :rofl:

Hi again,

I coded a version based on Vispy colorbars and is faster than Matplotlib. Here the fragment:

import napari

from nilearn import datasets
from nilearn import surface

from qtpy.QtWidgets import QVBoxLayout, QWidget
import vispy as vp
from vispy import scene
from vispy.scene import widgets
import numpy as np

nki_dataset = datasets.fetch_surf_nki_enhanced(n_subjects=1)
fsaverage = datasets.fetch_surf_fsaverage()

brain_vertices, brain_faces = surface.load_surf_data(fsaverage['pial_left'])
timeseries = surface.load_surf_data(nki_dataset['func_left'][0])
timeseries = timeseries.transpose((1, 0))

COLORMAP_STEP = 8

def axis_changed(event):

    idx_max = viewer.dims.range[0][1]
    idx = viewer.dims.current_step[0]

    if idx < idx_max and layer.visible:
        random_number = np.round(np.random.random(), decimals=1)
        colorbar1.clim = (random_number, random_number+1)
        colorbar2.clim = (random_number, random_number+1)
        colorbar3.clim = (random_number, random_number+1)

with napari.gui_qt():

    viewer = napari.Viewer(ndisplay=3)

    # COLORBARS
    width = 300
    height = 50
    padding = (.4, .4)
    ratio = 0.1

    canvas1 = scene.SceneCanvas(size=(width, height))
    cmap1 = vp.color.get_colormap('turbo')
    colorbar1 = widgets.ColorBarWidget(cmap1, 'top', "", 'white',
                        clim=(0, 1),
                        border_width=1.,
                        border_color='yellow',
                        padding=padding,
                        axis_ratio=ratio
                        )
    canvas1.central_widget.add_widget(colorbar1)

    canvas2 = scene.SceneCanvas(size=(width, height))
    cmap2 = vp.color.get_colormap('turbo')
    colorbar2 = widgets.ColorBarWidget(cmap2, 'top', "", 'white',
                        clim=(0, 1),
                        border_width=1.,
                        border_color='yellow',
                        padding=padding,
                        axis_ratio=ratio
                        )
    canvas2.central_widget.add_widget(colorbar2)

    canvas3 = scene.SceneCanvas(size=(width, height))
    base_cmap = vp.color.get_colormap('gist_rainbow')
    colors = base_cmap[np.linspace(0., 1., num=COLORMAP_STEP)]
    cmap3 = vp.color.Colormap(colors, interpolation='zero')
    colorbar3 = widgets.ColorBarWidget(cmap3, 'top', "", 'white',
                        clim=(0, 1),
                        border_width=1.,
                        border_color='yellow',
                        padding=padding,
                        axis_ratio=ratio
                        )
    canvas3.central_widget.add_widget(colorbar3)

    layout = QVBoxLayout()
    colorbars_widget = QWidget()
    layout.addWidget(canvas1.native)
    layout.addWidget(canvas2.native)
    layout.addWidget(canvas3.native)
    colorbars_widget.setLayout(layout)

    layer = viewer.add_surface((brain_vertices, brain_faces, timeseries))
    layer.colormap = ('custom', cmap1)

    viewer.dims.events.axis.connect(axis_changed)

    colorbars_widget.setFixedWidth(300)
    colorbars_widget.setFixedHeight(200)
    
    colorbars_dock = viewer.window.add_dock_widget(colorbars_widget, name='Colorbars', area = 'left')

The disadvantage is that Vispy colorbar seems to be harder to personalize ticks, label position, etc. :thinking:

Anyway, possibly a better approach exists, but I think that this way works fine :grinning:

2 Likes