Definindo o ponto médio de um mapa de cores em matplotlib

87

Quero definir o ponto médio de um mapa de cores, ou seja, meus dados vão de -5 a 10, quero que zero seja o meio. Acho que a maneira de fazer isso é normalizar a subclasse e usar a norma, mas não encontrei nenhum exemplo e não está claro para mim o que exatamente devo implementar.

Tiltstênio
fonte
isso é chamado de mapa de cores "divergente" ou "bipolar", em que o ponto central do mapa é importante e os dados vão acima e abaixo desse ponto. sandia.gov/~kmorel/documents/ColorMaps
endolith
3
Todas as respostas neste tópico parecem bastante complicadas. A solução fácil de usar é mostrada nesta excelente resposta , que entretanto também foi incluída na documentação matplotlib, seção Normalização personalizada: Duas faixas lineares .
ImportanceOfBeingErnest

Respostas:

14

Observe que no matplotlib versão 3.1 a classe DivergingNorm foi adicionada. Acho que cobre seu caso de uso. Ele pode ser usado assim:

from matplotlib import colors
colors.DivergingNorm(vmin=-4000., vcenter=0., vmax=10000)

No matplotlib 3.2, a classe foi renomeada para TwoSlopesNorm

macKaiver
fonte
Isso parece interessante, mas parece que deve ser usado para transformar os dados antes da plotagem. A legenda da barra de cores estará relacionada aos dados transformados, não aos originais.
bli
3
@bli não é o caso. o normfaz a normalização para sua imagem. normsande de mãos dadas com os mapas de cores.
Paul H
1
Infelizmente, isso está obsoleto a partir de 3.2 sem nenhum documento sobre como substituí-lo: matplotlib.org/3.2.0/api/_as_gen/…
daknowles
1
Sim, os documentos não são claros. Acho que foi renomeado para TwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/…
macKaiver
91

Sei que é tarde para o jogo, mas acabei de passar por esse processo e descobri uma solução que talvez seja menos robusta do que normalizar a criação de subclasses, mas muito mais simples. Achei que seria bom compartilhar aqui para a posteridade.

A função

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid

def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'):
    '''
    Function to offset the "center" of a colormap. Useful for
    data with a negative min and positive max and you want the
    middle of the colormap's dynamic range to be at zero.

    Input
    -----
      cmap : The matplotlib colormap to be altered
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower offset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to 
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax / (vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highest point in the colormap's range.
          Defaults to 1.0 (no upper offset). Should be between
          `midpoint` and 1.0.
    '''
    cdict = {
        'red': [],
        'green': [],
        'blue': [],
        'alpha': []
    }

    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)

    # shifted index to match the data
    shift_index = np.hstack([
        np.linspace(0.0, midpoint, 128, endpoint=False), 
        np.linspace(midpoint, 1.0, 129, endpoint=True)
    ])

    for ri, si in zip(reg_index, shift_index):
        r, g, b, a = cmap(ri)

        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))

    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)

    return newcmap

Um exemplo

biased_data = np.random.random_integers(low=-15, high=5, size=(37,37))

orig_cmap = matplotlib.cm.coolwarm
shifted_cmap = shiftedColorMap(orig_cmap, midpoint=0.75, name='shifted')
shrunk_cmap = shiftedColorMap(orig_cmap, start=0.15, midpoint=0.75, stop=0.85, name='shrunk')

fig = plt.figure(figsize=(6,6))
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.5,
                label_mode="1", share_all=True,
                cbar_location="right", cbar_mode="each",
                cbar_size="7%", cbar_pad="2%")

# normal cmap
im0 = grid[0].imshow(biased_data, interpolation="none", cmap=orig_cmap)
grid.cbar_axes[0].colorbar(im0)
grid[0].set_title('Default behavior (hard to see bias)', fontsize=8)

im1 = grid[1].imshow(biased_data, interpolation="none", cmap=orig_cmap, vmax=15, vmin=-15)
grid.cbar_axes[1].colorbar(im1)
grid[1].set_title('Centered zero manually,\nbut lost upper end of dynamic range', fontsize=8)

im2 = grid[2].imshow(biased_data, interpolation="none", cmap=shifted_cmap)
grid.cbar_axes[2].colorbar(im2)
grid[2].set_title('Recentered cmap with function', fontsize=8)

im3 = grid[3].imshow(biased_data, interpolation="none", cmap=shrunk_cmap)
grid.cbar_axes[3].colorbar(im3)
grid[3].set_title('Recentered cmap with function\nand shrunk range', fontsize=8)

for ax in grid:
    ax.set_yticks([])
    ax.set_xticks([])

Resultados do exemplo:

insira a descrição da imagem aqui

Paul H
fonte
Muito obrigado pela sua contribuição incrível! No entanto, o código não era capaz de tanto corte e deslocando o mesmo mapa de cores, e suas instruções eram uma imprecisa pouco e enganosa. Já consertei isso e tomei a liberdade de editar sua postagem. Além disso, eu o incluí em uma de minhas bibliotecas pessoais e adicionei você como autor. Eu espero que você não se importe.
TheChymera
@TheChymera o mapa de cores no canto inferior direito foi cortado e recentrado. Sinta-se à vontade para usar isso como quiser.
Paul H
Sim, infelizmente, só parece certo como uma coincidência. Se starte stopnão forem 0 e 1 respectivamente, depois de fazer isso reg_index = np.linspace(start, stop, 257), você não pode mais assumir que o valor 129 é o ponto médio do cmap original, portanto, todo o reescalonamento não faz sentido sempre que você corta. Além disso, startdeve ser de 0 a 0,5 e stopde 0,5 a 1, não ambos de 0 a 1 como você instrui.
TheChymera
@TheChymera Experimentei sua versão e pensei duas vezes a respeito. 1) parece-me que os índices que você criou são todos de comprimento 257, e em matplotlib o padrão é 256, presumo? 2) suponha que meus dados variam de -1 a 1000, eles são dominados por positivos e, portanto, mais níveis / camadas devem ir para o ramo positivo. Mas sua função fornece 128 níveis para negativos e positivos, então seria mais "justo" dividir os níveis de maneira desigual, eu acho.
Jason,
Esta é uma solução excelente, mas falha se o midpointdos dados for igual a 0 ou 1. Veja minha resposta abaixo para uma solução simples para esse problema.
DaveTheScientist de
22

Aqui está uma solução de subclasse Normalize. Para usá-lo

norm = MidPointNorm(midpoint=3)
imshow(X, norm=norm)

Aqui está a aula:

import numpy as np
from numpy import ma
from matplotlib import cbook
from matplotlib.colors import Normalize

class MidPointNorm(Normalize):    
    def __init__(self, midpoint=0, vmin=None, vmax=None, clip=False):
        Normalize.__init__(self,vmin, vmax, clip)
        self.midpoint = midpoint

    def __call__(self, value, clip=None):
        if clip is None:
            clip = self.clip

        result, is_scalar = self.process_value(value)

        self.autoscale_None(result)
        vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint

        if not (vmin < midpoint < vmax):
            raise ValueError("midpoint must be between maxvalue and minvalue.")       
        elif vmin == vmax:
            result.fill(0) # Or should it be all masked? Or 0.5?
        elif vmin > vmax:
            raise ValueError("maxvalue must be bigger than minvalue")
        else:
            vmin = float(vmin)
            vmax = float(vmax)
            if clip:
                mask = ma.getmask(result)
                result = ma.array(np.clip(result.filled(vmax), vmin, vmax),
                                  mask=mask)

            # ma division is very slow; we can take a shortcut
            resdat = result.data

            #First scale to -1 to 1 range, than to from 0 to 1.
            resdat -= midpoint            
            resdat[resdat>0] /= abs(vmax - midpoint)            
            resdat[resdat<0] /= abs(vmin - midpoint)

            resdat /= 2.
            resdat += 0.5
            result = ma.array(resdat, mask=result.mask, copy=False)                

        if is_scalar:
            result = result[0]            
        return result

    def inverse(self, value):
        if not self.scaled():
            raise ValueError("Not invertible until scaled")
        vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint

        if cbook.iterable(value):
            val = ma.asarray(value)
            val = 2 * (val-0.5)  
            val[val>0]  *= abs(vmax - midpoint)
            val[val<0] *= abs(vmin - midpoint)
            val += midpoint
            return val
        else:
            val = 2 * (value - 0.5)
            if val < 0: 
                return  val*abs(vmin-midpoint) + midpoint
            else:
                return  val*abs(vmax-midpoint) + midpoint
Tiltstênio
fonte
É possível usar esta classe além de escalonamento de log ou sym-log sem ter que criar mais subclasses? Meu caso de uso atual já usa "norm = SymLogNorm (linthresh = 1)"
AnnanFay
Perfeito, isso é exatamente o que eu estava procurando. Talvez você deva adicionar uma imagem para demonstrar a diferença? Aqui, o ponto médio é centralizado na barra, ao contrário de outros normalizadores de ponto médio, onde o ponto médio pode ser arrastado para as extremidades.
Gaborous
18

É mais fácil apenas usar os argumentos vmine vmaxpara imshow(assumindo que você está trabalhando com dados de imagem) em vez de criar subclasses matplotlib.colors.Normalize.

Por exemplo

import numpy as np
import matplotlib.pyplot as plt

data = np.random.random((10,10))
# Make the data range from about -5 to 10
data = 10 / 0.75 * (data - 0.25)

plt.imshow(data, vmin=-10, vmax=10)
plt.colorbar()

plt.show()

insira a descrição da imagem aqui

Joe Kington
fonte
1
É possível ter o exemplo atualizado para uma curva gaussiana para que possamos ver melhor a gradação da cor?
Dat Chu
3
Não gosto desta solução, pois não utiliza toda a gama dinâmica de cores disponíveis. Também gostaria de dar um exemplo de normalização para construir um symlog tipo de normalização.
até o
2
@tillsten - Estou confuso, então ... Você não pode usar a faixa dinâmica completa da barra de cores se quiser 0 no meio, certo? Você está querendo uma escala não linear, então? Uma escala para valores acima de 0, uma para valores abaixo? Nesse caso, sim, você precisará criar uma subclasse Normalize. Vou adicionar um exemplo daqui a pouco (supondo que outra pessoa não chegue antes de mim ...).
Joe Kington
@Joe: Você está certo, não é linear (mais exatamente, duas partes lineares). Usando vmin / vmax, a gama de cores para os valores menores que -5 não é usada (o que faz sentido em alguns aplicativos, mas não no meu).
até
2
para dados genéricos em Z:vmax=abs(Z).max(), vmin=-abs(Z).max()
endolith
12

Aqui eu crio uma subclasse de Normalizeseguido por um exemplo mínimo.

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt


class MidpointNormalize(mpl.colors.Normalize):
    def __init__(self, vmin, vmax, midpoint=0, clip=False):
        self.midpoint = midpoint
        mpl.colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        normalized_min = max(0, 1 / 2 * (1 - abs((self.midpoint - self.vmin) / (self.midpoint - self.vmax))))
        normalized_max = min(1, 1 / 2 * (1 + abs((self.vmax - self.midpoint) / (self.midpoint - self.vmin))))
        normalized_mid = 0.5
        x, y = [self.vmin, self.midpoint, self.vmax], [normalized_min, normalized_mid, normalized_max]
        return np.ma.masked_array(np.interp(value, x, y))


vals = np.array([[-5., 0], [5, 10]]) 
vmin = vals.min()
vmax = vals.max()

norm = MidpointNormalize(vmin=vmin, vmax=vmax, midpoint=0)
cmap = 'RdBu_r' 

plt.imshow(vals, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()

Resultado: pic-1

O mesmo exemplo apenas com dados positivos vals = np.array([[1., 3], [6, 10]])

pic-2

Propriedades:

  • O ponto médio fica com a cor do meio.
  • Intervalos superior e inferior são redimensionados pela mesma transformação linear.
  • Apenas as cores que aparecem na imagem são mostradas na barra de cores.
  • Parece funcionar bem mesmo se vminfor maior do que midpoint(não testei todos os casos extremos).

Esta solução é inspirada em uma classe com o mesmo nome desta página

icemtel
fonte
3
Melhor resposta devido à sua simplicidade. As outras respostas são melhores apenas se você já for um especialista em Matplotlib tentando se tornar um super especialista. A maioria dos buscadores de respostas do matplotlib está apenas tentando fazer algo para ir para a casa de seu cachorro e / ou família, e para eles esta resposta é a melhor.
sapo_cosmico
Esta solução parece mesmo a melhor, mas não funciona! Acabei de executar o script de teste e o resultado é completamente diferente (incluindo apenas quadrados azuis e nenhum vermelho). @icemtel, você pode verificar? (ao lado do problema com o recuo no def __call__)
Filipe
Ok, encontrei o (s) problema (s): os números no cálculo de normalized_mine normalized_maxsão considerados inteiros. Basta colocá-los como 0,0. Além disso, para obter a saída correta de sua figura, tive que usar vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) . Obrigado pela resposta, de qualquer maneira!
Filipe
Olá @Filipe, não consigo reproduzir o seu problema na minha máquina (Python 3.7, matplotlib 2.2.3 e acho que deve ser o mesmo nas versões mais recentes). Que versão você tem? De qualquer forma, fiz uma pequena edição tornando o array do tipo float e resolvi o problema de indentação. Obrigado por apontar isso
icemtel
Hmm .. Acabei de experimentar com python3 e também funciona. Mas estou usando o python2.7. Obrigado por corrigir e pela resposta. É muito simples de usar! :)
Filipe
5

Não tenho certeza se você ainda está procurando uma resposta. Para mim, tentar criar uma subclasse Normalizenão teve sucesso. Então, me concentrei em criar manualmente um novo conjunto de dados, ticks e tick-labels para obter o efeito que acho que você deseja.

Eu encontrei o scalemódulo em matplotlib que tem uma classe usada para transformar plotagens de linha pelas regras 'syslog', então eu uso isso para transformar os dados. Em seguida, dimensiono os dados para que vão de 0 a 1 (o que Normalizegeralmente acontece), mas dimensiono os números positivos de maneira diferente dos números negativos. Isso ocorre porque seus vmax e vmin podem não ser os mesmos, então 0,5 -> 1 pode cobrir um intervalo positivo maior do que 0,5 -> 0, o intervalo negativo cobre. Foi mais fácil para mim criar uma rotina para calcular os valores de escala e rótulo.

Abaixo está o código e uma figura de exemplo.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mpl as mpl
import matplotlib.scale as scale

NDATA = 50
VMAX=10
VMIN=-5
LINTHRESH=1e-4

def makeTickLables(vmin,vmax,linthresh):
    """
    make two lists, one for the tick positions, and one for the labels
    at those positions. The number and placement of positive labels is 
    different from the negative labels.
    """
    nvpos = int(np.log10(vmax))-int(np.log10(linthresh))
    nvneg = int(np.log10(np.abs(vmin)))-int(np.log10(linthresh))+1
    ticks = []
    labels = []
    lavmin = (np.log10(np.abs(vmin)))
    lvmax = (np.log10(np.abs(vmax)))
    llinthres = int(np.log10(linthresh))
    # f(x) = mx+b
    # f(llinthres) = .5
    # f(lavmin) = 0
    m = .5/float(llinthres-lavmin)
    b = (.5-llinthres*m-lavmin*m)/2
    for itick in range(nvneg):
        labels.append(-1*float(pow(10,itick+llinthres)))
        ticks.append((b+(itick+llinthres)*m))
    # add vmin tick
    labels.append(vmin)
    ticks.append(b+(lavmin)*m)

    # f(x) = mx+b
    # f(llinthres) = .5
    # f(lvmax) = 1
    m = .5/float(lvmax-llinthres)
    b = m*(lvmax-2*llinthres) 
    for itick in range(1,nvpos):
        labels.append(float(pow(10,itick+llinthres)))
        ticks.append((b+(itick+llinthres)*m))
    # add vmax tick
    labels.append(vmax)
    ticks.append(b+(lvmax)*m)

    return ticks,labels


data = (VMAX-VMIN)*np.random.random((NDATA,NDATA))+VMIN

# define a scaler object that can transform to 'symlog'
scaler = scale.SymmetricalLogScale.SymmetricalLogTransform(10,LINTHRESH)
datas = scaler.transform(data)

# scale datas so that 0 is at .5
# so two seperate scales, one for positive and one for negative
data2 = np.where(np.greater(data,0),
                 .75+.25*datas/np.log10(VMAX),
                 .25+.25*(datas)/np.log10(np.abs(VMIN))
                 )

ticks,labels=makeTickLables(VMIN,VMAX,LINTHRESH)

cmap = mpl.cm.jet
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(data2,cmap=cmap,vmin=0,vmax=1)
cbar = plt.colorbar(im,ticks=ticks)
cbar.ax.set_yticklabels(labels)

fig.savefig('twoscales.png')

vmax = 10, vmin = -5 e linthresh = 1e-4

Sinta-se à vontade para ajustar as "constantes" (por exemplo VMAX) na parte superior do script para confirmar se ele se comporta bem.

Yann
fonte
Obrigado pela sugestão, como pode ser visto a seguir, tive sucesso na subclasse. Mas seu código ainda é muito útil para fazer os ticklabels corretos.
até
4

Eu estava usando a excelente resposta de Paul H, mas tive um problema porque alguns dos meus dados variaram de negativo a positivo, enquanto outros conjuntos variaram de 0 a positivo ou de negativo a 0; em ambos os casos, eu queria que 0 fosse colorido como branco (o ponto médio do mapa de cores que estou usando). Com a implementação existente, se seu midpointvalor for igual a 1 ou 0, os mapeamentos originais não foram substituídos. Você pode ver isso na imagem a seguir: gráficos antes de editar A 3ª coluna parece correta, mas a área azul escura na 2ª coluna e a área vermelha escura nas colunas restantes devem ser todas brancas (seus valores de dados são, na verdade, 0). Usar minha correção me dá: gráficos após edição Minha função é essencialmente a mesma de Paul H, com minhas edições no início do forloop:

def shiftedColorMap(cmap, min_val, max_val, name):
    '''Function to offset the "center" of a colormap. Useful for data with a negative min and positive max and you want the middle of the colormap's dynamic range to be at zero. Adapted from /programming/7404116/defining-the-midpoint-of-a-colormap-in-matplotlib

    Input
    -----
      cmap : The matplotlib colormap to be altered.
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower ofset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax/(vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highets point in the colormap's range.
          Defaults to 1.0 (no upper ofset). Should be between
          `midpoint` and 1.0.'''
    epsilon = 0.001
    start, stop = 0.0, 1.0
    min_val, max_val = min(0.0, min_val), max(0.0, max_val) # Edit #2
    midpoint = 1.0 - max_val/(max_val + abs(min_val))
    cdict = {'red': [], 'green': [], 'blue': [], 'alpha': []}
    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)
    # shifted index to match the data
    shift_index = np.hstack([np.linspace(0.0, midpoint, 128, endpoint=False), np.linspace(midpoint, 1.0, 129, endpoint=True)])
    for ri, si in zip(reg_index, shift_index):
        if abs(si - midpoint) < epsilon:
            r, g, b, a = cmap(0.5) # 0.5 = original midpoint.
        else:
            r, g, b, a = cmap(ri)
        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))
    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)
    return newcmap

EDIT: Eu tive um problema semelhante mais uma vez quando alguns dos meus dados variaram de um valor positivo pequeno a um valor positivo maior, onde os valores muito baixos estavam sendo coloridos em vermelho em vez de branco. Eu consertei adicionando uma linha Edit #2no código acima.

DaveTheScientist
fonte
Parece bom, mas parece que os argumentos mudaram em relação à resposta de Paul H (e os comentários) ... Você pode adicionar um exemplo de chamada à sua resposta?
Filipe
1

Se você não se importa em calcular a proporção entre vmin, vmax e zero, este é um mapa linear bastante básico de azul para branco e vermelho, que define o branco de acordo com a proporção z:

def colormap(z):
    """custom colourmap for map plots"""

    cdict1 = {'red': ((0.0, 0.0, 0.0),
                      (z,   1.0, 1.0),
                      (1.0, 1.0, 1.0)),
              'green': ((0.0, 0.0, 0.0),
                        (z,   1.0, 1.0),
                        (1.0, 0.0, 0.0)),
              'blue': ((0.0, 1.0, 1.0),
                       (z,   1.0, 1.0),
                       (1.0, 0.0, 0.0))
              }

    return LinearSegmentedColormap('BlueRed1', cdict1)

O formato cdict é bastante simples: as linhas são pontos no gradiente que é criado: a primeira entrada é o valor x (a proporção ao longo do gradiente de 0 a 1), o segundo é o valor final para o segmento anterior, e o terceiro é o valor inicial para o próximo segmento - se você quiser gradientes suaves, os dois últimos são sempre os mesmos. Veja a documentação para mais detalhes.

naught101
fonte
1
Também existe a opção de especificar dentro das LinearSegmentedColormap.from_list()tuplas (val,color)e passá-las como lista para o colorargumento deste método onde val0=0<val1<...<valN==1.
maurizio
0

Tive um problema semelhante, mas queria que o valor mais alto fosse totalmente vermelho e eliminasse os valores baixos de azul, fazendo com que parecesse essencialmente que a parte inferior da barra de cores foi cortada. Isso funcionou para mim (inclui transparência opcional):

def shift_zero_bwr_colormap(z: float, transparent: bool = True):
    """shifted bwr colormap"""
    if (z < 0) or (z > 1):
        raise ValueError('z must be between 0 and 1')

    cdict1 = {'red': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
                      (z,   1.0, 1.0),
                      (1.0, 1.0, 1.0)),

              'green': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
                        (z,   1.0, 1.0),
                        (1.0, max(2*z-1,0),  max(2*z-1,0))),

              'blue': ((0.0, 1.0, 1.0),
                       (z,   1.0, 1.0),
                       (1.0, max(2*z-1,0), max(2*z-1,0))),
              }
    if transparent:
        cdict1['alpha'] = ((0.0, 1-max(-2*z+1, 0), 1-max(-2*z+1, 0)),
                           (z,   0.0, 0.0),
                           (1.0, 1-max(2*z-1,0),  1-max(2*z-1,0)))

    return LinearSegmentedColormap('shifted_rwb', cdict1)

cmap =  shift_zero_bwr_colormap(.3)

x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 5 + 5
plt.plot([0, 10*np.pi], [0, 20*np.pi], color='c', lw=20, zorder=-3)
plt.imshow(Z, interpolation='nearest', origin='lower', cmap=cmap)
plt.colorbar()
ben.dichter
fonte