Como posso definir a proporção da imagem em matplotlib?

108

Estou tentando fazer um gráfico quadrado (usando imshow), ou seja, proporção de 1: 1, mas não consigo. Nenhum desses funciona:

import matplotlib.pyplot as plt

ax = fig.add_subplot(111,aspect='equal')
ax = fig.add_subplot(111,aspect=1.0)
ax.set_aspect('equal')
plt.axes().set_aspect('equal')

Parece que as ligações estão sendo simplesmente ignoradas (um problema que freqüentemente pareço ter com matplotlib).

jtlz2
fonte
6
Você tentou ax.axis('equal'), por acaso? Como todos disseram, o que você fez deve funcionar, mas ax.axispode ser outro caminho para tentar uma solução alternativa.
Joe Kington

Respostas:

77

Terceira vez o charme. Meu palpite é que isso é um bug e a resposta de Zhenya sugere que ele foi corrigido na versão mais recente. Tenho a versão 0.99.1.1 e criei a seguinte solução:

import matplotlib.pyplot as plt
import numpy as np

def forceAspect(ax,aspect=1):
    im = ax.get_images()
    extent =  im[0].get_extent()
    ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_xlabel('xlabel')
ax.set_aspect(2)
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')
forceAspect(ax,aspect=1)
fig.savefig('force.png')

Este é 'force.png': insira a descrição da imagem aqui

Abaixo estão minhas tentativas infrutíferas, mas esperançosamente informativas.

Segunda Resposta:

Minha 'resposta original' abaixo é exagerada, pois faz algo semelhante a axes.set_aspect(). Eu acho que você quer usar axes.set_aspect('auto'). Não entendo porque é o caso, mas produz um gráfico de imagem quadrada para mim, por exemplo este script:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_aspect('equal')
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')

Produz um gráfico de imagem com proporção de aspecto 'igual': insira a descrição da imagem aqui e um com proporção de aspecto 'automático': insira a descrição da imagem aqui

O código fornecido abaixo na 'resposta original' fornece um ponto de partida para uma proporção de aspecto explicitamente controlada, mas parece ser ignorado quando um imshow é chamado.

Resposta Original:

Aqui está um exemplo de rotina que ajustará os parâmetros do subplot para que você obtenha a proporção de aspecto desejada:

import matplotlib.pyplot as plt

def adjustFigAspect(fig,aspect=1):
    '''
    Adjust the subplot parameters so that the figure has the correct
    aspect ratio.
    '''
    xsize,ysize = fig.get_size_inches()
    minsize = min(xsize,ysize)
    xlim = .4*minsize/xsize
    ylim = .4*minsize/ysize
    if aspect < 1:
        xlim *= aspect
    else:
        ylim /= aspect
    fig.subplots_adjust(left=.5-xlim,
                        right=.5+xlim,
                        bottom=.5-ylim,
                        top=.5+ylim)

fig = plt.figure()
adjustFigAspect(fig,aspect=.5)
ax = fig.add_subplot(111)
ax.plot(range(10),range(10))

fig.savefig('axAspect.png')

Isso produz uma figura como esta: insira a descrição da imagem aqui

Eu posso imaginar se você tiver vários subplots dentro da figura, você gostaria de incluir o número de yex subplots como parâmetros de palavra-chave (padronizando para 1 cada) para a rotina fornecida. Então, usando esses números e as palavras hspace- wspacechave e , você pode fazer com que todos os subtramas tenham a proporção correta.

Yann
fonte
1
Para os casos em que get_imagesé uma lista vazia (como aconteceria com ax.plot([0,1],[0,2]), você pode usar get_xlimeget_ylim
Joel
Parece-me que isso não funcionará se for feito com logscale. Eu adicionei uma resposta que testa e lida com isso. Sinta-se à vontade para incorporar isso à sua resposta e então removerei a minha.
Joel
1
A razão pela qual o aspecto parece desigual é porque aspecto igual significa que a distância visual em x será igual a y. Se a imagem for quadrada, mas o gráfico dx e dy forem diferentes, então essa não é uma proporção de 1: 1. A relação de aspecto será dy / dx nesse caso.
bart cubrich
22

Qual é a matplotlibversão que você está executando? Recentemente, tive que atualizar para 1.1.0, e com ele, add_subplot(111,aspect='equal')funciona para mim.

ev-br
fonte
1
Funciona bem para mim na matplotlibversão 2.0.2. jupyter notebookversão 5.0.0. Obrigado.
Sathish,
5

Depois de muitos anos de sucesso com as respostas acima, descobri que isso não funcionava novamente - mas encontrei uma solução de trabalho para subtramas em

https://jdhao.github.io/2017/06/03/change-aspect-ratio-in-mpl

Com todo o crédito, é claro, do autor acima (que talvez prefira postar aqui), as linhas relevantes são:

ratio = 1.0
xleft, xright = ax.get_xlim()
ybottom, ytop = ax.get_ylim()
ax.set_aspect(abs((xright-xleft)/(ybottom-ytop))*ratio)

O link também tem uma explicação muito clara dos diferentes sistemas de coordenadas usados ​​pelo matplotlib.

Obrigado por todas as ótimas respostas recebidas - especialmente a de @ Yann, que continuará sendo a vencedora.

jtlz2
fonte
3

você deve tentar com figaspect. Funciona para mim. Dos documentos:

Crie uma figura com a proporção de aspecto especificada. Se arg for um número, use essa proporção. > Se arg for uma matriz, o figaspect determinará a largura e a altura de uma figura que caberia na relação de aspecto de preservação da matriz. A largura da figura e a altura em polegadas são retornadas. Certifique-se de criar eixos iguais a e altura, por exemplo

Exemplo de uso:

  # make a figure twice as tall as it is wide
  w, h = figaspect(2.)
  fig = Figure(figsize=(w,h))
  ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
  ax.imshow(A, **kwargs)

  # make a figure with the proper aspect for an array
  A = rand(5,3)
  w, h = figaspect(A)
  fig = Figure(figsize=(w,h))
  ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
  ax.imshow(A, **kwargs)

Edit: Não tenho certeza do que você está procurando. O código acima muda a tela (o tamanho do gráfico). Se você quiser alterar o tamanho da janela matplotlib da figura, use:

In [68]: f = figure(figsize=(5,1))

isso produz uma janela de 5x1 (largura x altura).

joaquin
fonte
Obrigado por isso - tem algum efeito, ao alterar a proporção da tela: Para ser mais específico, eu preciso alterar a proporção da própria figura, o que fazer o seguinte não muda (formatação apols ..): fig = plt.figure (figsize = (plt.figaspect (2.0)))
jtlz2 01 de
3

Essa resposta é baseada na resposta de Yann. Ele definirá a proporção de aspecto para gráficos lineares ou log-log. Usei informações adicionais de https://stackoverflow.com/a/16290035/2966723 para testar se os eixos são em escala logarítmica.

def forceAspect(ax,aspect=1):
    #aspect is width/height
    scale_str = ax.get_yaxis().get_scale()
    xmin,xmax = ax.get_xlim()
    ymin,ymax = ax.get_ylim()
    if scale_str=='linear':
        asp = abs((xmax-xmin)/(ymax-ymin))/aspect
    elif scale_str=='log':
        asp = abs((scipy.log(xmax)-scipy.log(xmin))/(scipy.log(ymax)-scipy.log(ymin)))/aspect
    ax.set_aspect(asp)

Obviamente, você pode usar qualquer versão logque quiser, eu usei scipy, mas numpyou mathdeveria estar bem.

Joel
fonte