216 votes

Spécifier et enregistrer une figure avec une taille exacte en pixels

Disons que j'ai une image de 3841 x 7195 pixels. Je voudrais enregistrer le contenu de la figure sur le disque, ce qui donne une image de la taille de taille exacte Je spécifie en pixels.

Pas d'axe, pas de titres. Juste l'image. Personnellement, je ne me préoccupe pas des DPI, car je veux seulement spécifier la taille de l'image sur le disque à l'écran. en pixels .

J'ai lu autre fils Ils semblent tous effectuer des conversions en pouces, puis spécifier les dimensions de la figure en pouces et ajuster les ppp d'une manière ou d'une autre. J'aimerais éviter la perte potentielle de précision qui pourrait résulter des conversions de pixels en pouces.

J'ai essayé avec :

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)

sans succès (Python se plaint que la largeur et la hauteur doivent chacune être inférieures à 32768 ( ?))

D'après tout ce que j'ai vu, matplotlib exige que la taille de la figure soit spécifiée dans inches y dpi mais je ne suis intéressé que par les pixels la figure prend dans le disque. Comment puis-je faire cela ?

Pour clarifier : je cherche un moyen de faire cela avec matplotlib et non avec d'autres bibliothèques d'économie d'image.

236voto

tiago Points 4568

Matplotlib ne travaille pas directement avec les pixels, mais plutôt avec les tailles physiques et le DPI. Si vous voulez afficher une figure avec une certaine taille de pixel, vous devez connaître le DPI de votre moniteur. Par exemple ce lien détectera cela pour vous.

Si vous avez une image de 3841x7195 pixels, il est peu probable que votre écran soit aussi grand, vous ne pourrez donc pas afficher une figure de cette taille (matplotlib exige que la figure s'adapte à l'écran, si vous demandez une taille trop grande, elle sera réduite à la taille de l'écran). Imaginons que vous vouliez une image de 800x800 pixels juste pour un exemple. Voici comment afficher une image de 800x800 pixels sur mon écran ( my_dpi=96 ):

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

Il suffit donc de diviser les dimensions en pouces par votre DPI.

Si vous voulez enregistrer une figure d'une taille spécifique, c'est une autre affaire. Les DPI de l'écran ne sont plus si importants (sauf si vous demandez une figure qui ne rentre pas dans l'écran). En reprenant le même exemple de la figure de 800x800 pixels, nous pouvons l'enregistrer dans différentes résolutions en utilisant la fonction dpi mot clé de savefig . Pour l'enregistrer dans la même résolution que l'écran, il suffit d'utiliser la même valeur de dpi :

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

Pour l'enregistrer en tant qu'image de 8000x8000 pixels, utilisez un dpi 10 fois plus grand :

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

Notez que le réglage de la DPI n'est pas pris en charge par tous les backends. Ici, le backend PNG est utilisé, mais les backends pdf et ps implémenteront la taille différemment. En outre, la modification de la DPI et des tailles affectera également des éléments tels que la taille des polices. Un DPI plus élevé conservera les mêmes tailles relatives de polices et d'éléments, mais si vous voulez des polices plus petites pour une figure plus grande, vous devez augmenter la taille physique plutôt que le DPI.

Pour en revenir à votre exemple, si vous souhaitez enregistrer une image de 3841 x 7195 pixels, vous pouvez procéder comme suit :

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

Notez que j'ai utilisé le chiffre dpi de 100 pour tenir dans la plupart des écrans, mais que j'ai enregistré avec dpi=1000 pour atteindre la résolution requise. Dans mon système, cela produit un png de 3840x7190 pixels -- il semble que le DPI enregistré est toujours 0,02 pixels/pouce plus petit que la valeur sélectionnée, ce qui aura un (petit) effet sur les images de grande taille. D'autres discussions à ce sujet aquí .

24voto

heltonbiker Points 4725

Cela a fonctionné pour moi, sur la base de votre code, générant une image png de 93Mb avec du bruit de couleur et les dimensions souhaitées :

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)

J'utilise les dernières versions PIP des bibliothèques Python 2.7 dans Linux Mint 13.

J'espère que cela vous aidera !

12voto

L'OP veut préserver les données de pixel 1:1. En tant qu'astronome travaillant avec des images scientifiques, je ne peux pas autoriser l'interpolation des données d'image car cela introduirait des erreurs ou des bruits inconnus et imprévisibles. Par exemple, voici un extrait d'une image 480x480 enregistrée via pyplot.savefig() : Détail des pixels que matplotlib a rééchantillonné pour être approximativement 2x2, mais remarquez la colonne de pixels 1x2

Vous pouvez voir que la plupart des pixels ont simplement été doublés (un pixel 1x1 devient 2x2) mais que certaines colonnes et lignes sont devenues 1x2 ou 2x1 par pixel, ce qui signifie que les données scientifiques d'origine ont été modifiées.

Comme l'a suggéré Alka, plt.imsave() permet d'obtenir ce que l'OP demande. Disons que vous avez des données d'image stockées dans un tableau d'images im, alors on peut faire quelque chose comme

plt.imsave(fname='my_image.png', arr=im, cmap='gray_r', format='png')

où le nom de fichier a l'extension "png" dans cet exemple (mais vous devez toujours spécifier le format avec format='png' de toute façon pour autant que je puisse dire), le tableau d'images est arr, et nous avons choisi le niveau de gris inversé "gray_r" comme carte de couleurs. J'ajoute habituellement vmin et vmax pour spécifier la gamme dynamique, mais ils sont facultatifs.

Le résultat final est un fichier png ayant exactement les mêmes dimensions de pixels que le tableau im.

Note : le PO a spécifié qu'il n'y avait pas d'axes, etc., ce qui est exactement ce que fait cette solution. Si l'on veut ajouter des axes, des tics, etc., je préfère le faire sur un graphique séparé, en l'enregistrant avec transparent=Vrai (PNG ou PDF), puis en superposant ce dernier sur l'image. Cela garantit que vous avez gardé les pixels originaux intacts.

7voto

Cyril Points 71

Sur la base de la réponse acceptée par tiago, voici une petite fonction générique qui exporte un tableau numpy vers une image ayant la même résolution que le tableau :

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()

Comme indiqué dans la réponse précédente de tiago, le DPI de l'écran doit d'abord être trouvé, ce qui peut être fait ici par exemple : http://dpi.lv

J'ai ajouté un argument supplémentaire resize_fact dans la fonction qui vous permet d'exporter l'image à 50% (0,5) de la résolution originale, par exemple.

6voto

Ciro Santilli Points 3341

Comparaison de différentes approches

Voici une comparaison rapide de certaines des approches que j'ai essayées avec des images montrant ce qu'elles donnent.

Exemple de base sans essayer de définir les dimensions de l'image

Juste pour avoir un point de comparaison :

base.py

#!/usr/bin/env python3

import sys

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

fig, ax = plt.subplots()
print('fig.dpi = {}'.format(fig.dpi))
print('fig.get_size_inches() = ' + str(fig.get_size_inches())
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig('base.png', format='png')

courir :

./base.py
identify base.png

sorties :

fig.dpi = 100.0
fig.get_size_inches() = [6.4 4.8]
base.png PNG 640x480 640x480+0+0 8-bit sRGB 13064B 0.000u 0:00.000

enter image description here

Ma meilleure approche jusqu'à présent : plt.savefig(dpi=h/fig.get_size_inches()[1] contrôle en hauteur

Je pense que c'est ce que je choisirai la plupart du temps, car c'est simple et c'est une balance :

get_size.py

#!/usr/bin/env python3

import sys

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

height = int(sys.argv[1])
fig, ax = plt.subplots()
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'get_size.png',
    format='png',
    dpi=height/fig.get_size_inches()[1]
)

courir :

./get_size.py 431

sorties :

get_size.png PNG 574x431 574x431+0+0 8-bit sRGB 10058B 0.000u 0:00.000

enter image description here

y

./get_size.py 1293

sorties :

main.png PNG 1724x1293 1724x1293+0+0 8-bit sRGB 46709B 0.000u 0:00.000

enter image description here

J'ai tendance à ne définir que la hauteur car je suis généralement plus préoccupé par l'espace vertical que l'image va occuper au milieu de mon texte.

plt.savefig(bbox_inches='tight' modifie la taille de l'image

Je trouve toujours qu'il y a trop d'espace blanc autour des images, et j'ai eu tendance à ajouter des bbox_inches='tight' de : Suppression de l'espace blanc autour d'une image enregistrée dans matplotlib

Cependant, cela fonctionne en recadrant l'image, et vous n'obtiendrez pas les tailles souhaitées avec cette méthode.

Au contraire, cette autre approche proposée dans la même question semble bien fonctionner :

plt.tight_layout(pad=1)
plt.savefig(...

ce qui donne la hauteur exacte souhaitée pour une hauteur égale à 431 :

enter image description here

Hauteur fixe, set_aspect , largeur automatiquement dimensionnée et marges réduites

Ermmm, set_aspect gâche les choses à nouveau et empêche plt.tight_layout de supprimer réellement les marges...

Demandé à : Comment obtenir une hauteur fixe en pixels, un rapport d'aspect x/y des données fixe et supprimer automatiquement la marge horizontale d'espace blanc dans Matplotlib ?

plt.savefig(dpi=h/fig.get_size_inches()[1] + contrôle de la largeur

Si vous avez vraiment besoin d'une largeur spécifique en plus de la hauteur, cela semble fonctionner correctement :

width.py

#!/usr/bin/env python3

import sys

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

h = int(sys.argv[1])
w = int(sys.argv[2])
fig, ax = plt.subplots()
wi, hi = fig.get_size_inches()
fig.set_size_inches(hi*(w/h), hi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'width.png',
    format='png',
    dpi=h/hi
)

courir :

./width.py 431 869

sortie :

width.png PNG 869x431 869x431+0+0 8-bit sRGB 10965B 0.000u 0:00.000

enter image description here

et pour une petite largeur :

./width.py 431 869

sortie :

width.png PNG 211x431 211x431+0+0 8-bit sRGB 6949B 0.000u 0:00.000

enter image description here

Il semble donc que la mise à l'échelle des polices soit correcte, mais les étiquettes sont coupées dans les très petites largeurs, par exemple dans le cas de l'image suivante 100 en haut à gauche.

J'ai réussi à les contourner avec Suppression de l'espace blanc autour d'une image enregistrée dans matplotlib

plt.tight_layout(pad=1)

ce qui donne :

width.png PNG 211x431 211x431+0+0 8-bit sRGB 7134B 0.000u 0:00.000

enter image description here

De ce fait, nous voyons aussi que tight_layout supprime une grande partie de l'espace vide en haut de l'image, donc je l'utilise généralement toujours.

Hauteur de la base magique fixe, dpi en fig.set_size_inches y plt.savefig(dpi= mise à l'échelle

Je pense que cela équivaut à l'approche mentionnée à l'adresse suivante : https://stackoverflow.com/a/13714720/895245

magic.py

#!/usr/bin/env python3

import sys

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

magic_height = 300
w = int(sys.argv[1])
h = int(sys.argv[2])
dpi = 80
fig, ax = plt.subplots(dpi=dpi)
fig.set_size_inches(magic_height*w/(h*dpi), magic_height/dpi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'magic.png',
    format='png',
    dpi=h/magic_height*dpi,
)

courir :

./magic.py 431 231

sorties :

magic.png PNG 431x231 431x231+0+0 8-bit sRGB 7923B 0.000u 0:00.000

enter image description here

Et pour voir si ça se passe bien :

./magic.py 1291 693

sorties :

magic.png PNG 1291x693 1291x693+0+0 8-bit sRGB 25013B 0.000u 0:00.000

enter image description here

Nous constatons donc que cette approche donne également de bons résultats. Le seul problème que j'ai avec elle est que vous devez définir que magic_height ou équivalent.

DPI fixe + set_size_inches

Cette approche a donné une taille de pixel légèrement erronée, et il est difficile de mettre tout à l'échelle de manière transparente.

set_size_inches.py

#!/usr/bin/env python3

import sys

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

w = int(sys.argv[1])
h = int(sys.argv[2])
fig, ax = plt.subplots()
fig.set_size_inches(w/fig.dpi, h/fig.dpi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(
    0,
    60.,
    'Hello',
    # Keep font size fixed independently of DPI.
    # https://stackoverflow.com/questions/39395616/matplotlib-change-figsize-but-keep-fontsize-constant
    fontdict=dict(size=10*h/fig.dpi),
)
plt.savefig(
    'set_size_inches.png',
    format='png',
)

courir :

./set_size_inches.py 431 231

sorties :

set_size_inches.png PNG 430x231 430x231+0+0 8-bit sRGB 8078B 0.000u 0:00.000

donc la hauteur est légèrement décalée, et l'image :

enter image description here

La taille des pixels est également correcte si je la multiplie par trois :

./set_size_inches.py 1291 693

sorties :

set_size_inches.png PNG 1291x693 1291x693+0+0 8-bit sRGB 19798B 0.000u 0:00.000

enter image description here

Nous comprenons cependant que pour que cette approche soit bien adaptée, il faut que chaque paramètre dépendant du DPI soit proportionnel à la taille en pouces.

Dans l'exemple précédent, nous avons uniquement rendu le texte "Hello" proportionnel, et il a conservé sa hauteur entre 60 et 80 comme prévu. Mais tout ce pour quoi nous n'avons pas fait cela, semble minuscule, y compris :

  • largeur de ligne des axes
  • étiquettes à cocher
  • marqueurs de points

SVG

Je n'ai pas trouvé comment le définir pour les images SVG, mes approches ne fonctionnaient que pour les PNG par exemple :

get_size_svg.py

#!/usr/bin/env python3

import sys

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

height = int(sys.argv[1])
fig, ax = plt.subplots()
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'get_size_svg.svg',
    format='svg',
    dpi=height/fig.get_size_inches()[1]
)

courir :

./get_size_svg.py 431

et la sortie générée contient :

<svg height="345.6pt" version="1.1" viewBox="0 0 460.8 345.6" width="460.8pt"

et identifier dit :

get_size_svg.svg SVG 614x461 614x461+0+0 8-bit sRGB 17094B 0.000u 0:00.000

et si je l'ouvre dans Chromium 86, les outils de débogage du navigateur, le survol de l'image par la souris, confirment que la hauteur est de 460,79.

Mais bien sûr, puisque SVG est un format vectoriel, tout devrait en théorie être mis à l'échelle, vous pouvez donc simplement le convertir dans n'importe quel format de taille fixe sans perte de résolution, par exemple :

inkscape -h 431 get_size_svg.svg -b FFF -e get_size_svg.png

donne la hauteur exacte :

TODO régénérer l'image, j'ai raté le téléchargement d'une manière ou d'une autre.

J'utilise Inkscape au lieu de Imagemagick. convert ici parce que vous avez besoin de jouer avec -density également pour obtenir des redimensionnements SVG nets avec ImageMagick :

Et la mise <img height="" sur le HTML devrait également fonctionner pour le navigateur.

Testé sur matplotlib==3.2.2.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X