Especificando e salvando uma figura com tamanho exato em pixels

152

Digamos que eu tenha uma imagem do tamanho 3841 x 7195 pixels. Gostaria de salvar o conteúdo da figura em disco, resultando em uma imagem do tamanho exato especificado em pixels.

Sem eixo, sem títulos. Apenas a imagem. Pessoalmente, não me importo com DPIs, pois só quero especificar o tamanho da imagem na tela do disco em pixels .

Eu li outros tópicos , e todos parecem fazer conversões em polegadas e depois especificar as dimensões da figura em polegadas e ajustar os dpi de alguma forma. Gostaria de evitar lidar com a potencial perda de precisão que poderia resultar de conversões de pixel em polegadas.

Eu tentei com:

w = 7195
h = 3841
fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig(some_path, dpi=1)

sem sorte (Python reclama que largura e altura devem estar abaixo de 32768 (?))

De tudo o que vi, matplotlibrequer que o tamanho da figura seja especificado em inchese dpi, mas estou interessado apenas nos pixels que a figura ocupa no disco. Como posso fazer isso?

Para esclarecer: Estou procurando uma maneira de fazer isso com matplotlibe não com outras bibliotecas de salvamento de imagens.

Amelio Vazquez-Reina
fonte
Com o matplotlib, não é possível definir o tamanho da figura diretamente em polegadas.
tiago

Respostas:

175

O Matplotlib não funciona diretamente com pixels, mas com tamanhos físicos e DPI. Se você deseja exibir uma figura com um determinado tamanho de pixel, precisa conhecer o DPI do seu monitor. Por exemplo, este link detectará isso para você.

Se você tiver uma imagem de 3841x7195 pixels, é improvável que o monitor seja tão grande, portanto não será possível mostrar uma figura desse tamanho (o matplotlib exige que a figura caiba na tela, se você solicitar um tamanho muito grande, diminuirá para o tamanho da tela). Vamos imaginar que você queira uma imagem de 800x800 pixels apenas por exemplo. Veja como mostrar uma imagem de 800x800 pixels no meu monitor ( my_dpi=96):

plt.figure(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi)

Então você basicamente divide as dimensões em polegadas pelo seu DPI.

Se você deseja salvar uma figura de um tamanho específico, é uma questão diferente. Os DPIs da tela não são mais tão importantes (a menos que você solicite uma figura que não caiba na tela). Usando o mesmo exemplo da figura 800x800 pixel, que pode salvá-lo em diferentes resoluções usando a dpipalavra-chave de savefig. Para salvá-lo na mesma resolução que a tela, use o mesmo dpi:

plt.savefig('my_fig.png', dpi=my_dpi)

Para salvá-lo como uma imagem de 8000 x 8000 pixels, use um dpi 10 vezes maior:

plt.savefig('my_fig.png', dpi=my_dpi * 10)

Observe que a configuração do DPI não é suportada por todos os back-ends. Aqui, o backend PNG é usado, mas os backends pdf e ps implementam o tamanho de maneira diferente. Além disso, alterar o DPI e os tamanhos também afetará coisas como tamanho da fonte. Um DPI maior manterá os mesmos tamanhos relativos de fontes e elementos, mas se você quiser fontes menores para uma figura maior, precisará aumentar o tamanho físico em vez do DPI.

Voltando ao seu exemplo, se você quiser salvar uma imagem com 3841 x 7195 pixels, faça o seguinte:

plt.figure(figsize=(3.841, 7.195), dpi=100)
( your code ...)
plt.savefig('myfig.png', dpi=1000)

Observe que usei a figura dpi de 100 para caber na maioria das telas, mas salvei com dpi=1000para obter a resolução necessária. No meu sistema, isso produz um png com 3840x7190 pixels - parece que o DPI salvo é sempre 0,02 pixels / polegada menor que o valor selecionado, o que terá um efeito (pequeno) em tamanhos de imagem grandes. Mais algumas discussões sobre isso aqui .

Tiago
fonte
5
É útil lembrar que os tamanhos dos monitores (e, portanto, os tamanhos padrão dos navegadores e janelas da interface do usuário) são normalmente de 96 dpi - múltiplos de 96. De repente, números como 1440 pixels são significativos (15 polegadas) quando pensados ​​assim.
Danny Staple
Não foi possível chegar a este trabalho passando figsizepara plt.figure. A solução foi fazer o que as outras respostas sugerem e, depois de chamá-lo sem figsize , então chamefig.set_size_inches(w,h)
Trinidad
Documentos para figuree savefig.
manipula
O link não mostra o valor correto para o Apple Thunderbolt Display.
Dmitry
Eu gosto dessa solução, mas tenho um aviso. O tamanho do texto é dimensionado inversamente como dpi. (Meu sistema é MacBook Pro, OS X), portanto, para impressão interativa, o tamanho do dpi é grande (como 10 * my_dpi) reduz o texto à quase invisibilidade.
Prof Huster 14/10
16

Isso funcionou para mim, com base no seu código, gerando uma imagem png de 93Mb com ruído de cor e as dimensões desejadas:

import matplotlib.pyplot as plt
import numpy

w = 7195
h = 3841

im_np = numpy.random.rand(h, w)

fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig('figure.png', dpi=1)

Estou usando as últimas versões PIP das bibliotecas Python 2.7 no Linux Mint 13.

Espero que ajude!

heltonbiker
fonte
4
Definir um dpi muito baixo significa que as fontes dificilmente serão visíveis, a menos que tamanhos muito grandes sejam usados ​​explicitamente.
tiago
1
Provavelmente é melhor definir um dpi mais alto e dividir o tamanho da polegada (que é arbitrário de qualquer maneira) por esse dpi. Fora isso, sua configuração produz um pixel exato para a reprodução, obrigado!
FrenchKheldar
Estou tentando usar isso antes de salvar imagens com elementos de plotagem (círculos, linhas, ...). Isso atrapalha a largura da linha, de forma que os elementos são pouco visíveis.
Gauthier
5

Com base na resposta aceita por tiago, aqui está uma pequena função genérica que exporta um array numpy para uma imagem com a mesma resolução que o array:

import matplotlib.pyplot as plt
import numpy as np

def export_figure_matplotlib(arr, f_name, dpi=200, resize_fact=1, plt_show=False):
    """
    Export array as figure in original resolution
    :param arr: array of image to save in original resolution
    :param f_name: name of file where to save figure
    :param resize_fact: resize facter wrt shape of arr, in (0, np.infty)
    :param dpi: dpi of your screen
    :param plt_show: show plot or not
    """
    fig = plt.figure(frameon=False)
    fig.set_size_inches(arr.shape[1]/dpi, arr.shape[0]/dpi)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(arr)
    plt.savefig(f_name, dpi=(dpi * resize_fact))
    if plt_show:
        plt.show()
    else:
        plt.close()

Como dito na resposta anterior de tiago, o DPI da tela precisa ser encontrado primeiro, o que pode ser feito aqui, por exemplo: http://dpi.lv

Adicionei um argumento adicional resize_factna função que você pode exportar para 50% (0,5) da resolução original, por exemplo.

Cyril
fonte
-1

plt.imsave trabalhou para mim. Você pode encontrar a documentação aqui: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.imsave.html

#file_path = directory address where the image will be stored along with file name and extension
#array = variable where the image is stored. I think for the original post this variable is im_np
plt.imsave(file_path, array)
Alka
fonte
Adicione um código de exemplo mostrando exatamente qual parâmetro você está configurando e os valores recomendados para o caso de uso da postagem original.
Sarah Messer