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

152

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 exacte que je spécifie en pixels.

Pas d'axe, pas de titres. Juste l'image. Personnellement, je ne me soucie pas des DPI, car je souhaite uniquement spécifier la taille de l'image sur l'écran du disque en pixels .

J'ai lu d' autres fils , et ils semblent tous faire des conversions en pouces, puis spécifier les dimensions de la figure en pouces et ajuster les dpi d'une manière ou d'une autre. Je voudrais éviter de faire face à la perte de précision potentielle qui pourrait résulter des conversions pixel-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 chance (Python se plaint que la largeur et la hauteur doivent chacune être inférieures à 32768 (?))

De tout ce que j'ai vu, matplotlibnécessite que la taille de la figure soit spécifiée dans incheset dpi, mais je ne suis intéressé que par les pixels que la figure prend dans le disque. Comment puis-je faire ceci?

Pour clarifier: je cherche un moyen de le faire avec matplotlib, et non avec d'autres bibliothèques d'enregistrement d'images.

Amelio Vazquez-Reina
la source
Avec matplotlib, il n'est pas possible de définir la taille de la figure directement en pouces.
tiago

Réponses:

175

Matplotlib ne fonctionne pas directement avec les pixels, mais plutôt avec les tailles physiques et DPI. Si vous souhaitez 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 moniteur soit aussi grand, vous ne pourrez donc pas afficher un chiffre de cette taille (matplotlib nécessite que le chiffre tienne dans l'écran, si vous demandez une taille trop grand, il sera réduit à la taille de l'écran). Imaginons que vous vouliez une image de 800x800 pixels juste à titre d'exemple. Voici comment afficher une image de 800 x 800 pixels sur mon moniteur ( my_dpi=96):

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

Donc, vous divisez simplement les dimensions en pouces par votre DPI.

Si vous souhaitez 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 un chiffre qui ne rentre pas dans l'écran). En utilisant le même exemple de la figure 800x800 pixels, nous pouvons l'enregistrer dans différentes résolutions en utilisant le dpimot - clé de savefig. Pour l'enregistrer dans la même résolution que l'écran, utilisez simplement le même dpi:

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

Pour l'enregistrer sous forme d'image 8000x8000 pixels, utilisez un dpi 10 fois plus grand:

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

Notez que le paramètre 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 du DPI et des tailles affectera également des éléments tels que la taille de la police. Un DPI plus grand conservera les mêmes tailles relatives de polices et d'éléments, mais si vous voulez des polices plus petites pour un chiffre plus grand, vous devez augmenter la taille physique au lieu du DPI.

Pour revenir à votre exemple, si vous souhaitez enregistrer une image avec 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 s'adapter à la plupart des écrans, mais enregistré avec dpi=1000pour obtenir la résolution requise. Dans mon système, cela produit un png avec 3840x7190 pixels - il semble que le DPI enregistré soit toujours 0,02 pixels / pouce plus petit que la valeur sélectionnée, ce qui aura un (petit) effet sur les grandes tailles d'image. Un peu plus de discussion à ce sujet ici .

tiago
la source
5
Il est pratique de se rappeler que les tailles de moniteur (et donc les tailles de fenêtre standard du navigateur et de l'interface utilisateur) sont normalement en termes de 96 dpi - multiples de 96. Soudainement, des nombres comme 1440 pixels sont significatifs (15 pouces) quand on y pense comme ça.
Danny Staple
Impossible d' obtenir ce travail pour passer figsizeà plt.figure. La solution était de faire comme les autres réponses le suggèrent et après l'avoir appelé sans figsize , puis d'appelerfig.set_size_inches(w,h)
Trinidad
Documents pour figureet savefig.
gérer le
Le lien n'affiche pas la valeur correcte pour Apple Thunderbolt Display.
Dmitry
J'aime cette solution, mais j'ai un avertissement. La taille du texte s'échelonne inversement en dpi. (Mon système est MacBook Pro, OS X), donc pour l'impression interactive, rendre le dpi trop grand (comme 10 * my_dpi) réduit le texte à une quasi-invisibilité.
Prof Huster
16

Cela a fonctionné pour moi, en fonction de votre code, générant une image png de 93 Mo avec un 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 pourra aider!

heltonbiker
la source
4
Si vous définissez un dpi très bas, les polices seront à peine visibles, à moins que de très grandes tailles de police ne soient utilisées explicitement.
tiago
1
Il est probablement préférable de définir un dpi plus élevé et de diviser votre taille en pouces (ce qui est arbitraire de toute façon) par ce dpi. En dehors de cela, votre configuration produit un pixel exact pour la reproduction des pixels, merci!
FrenchKheldar
J'essaie de l'utiliser avant d'enregistrer des images avec des éléments de tracé (cercles, lignes, ...). Cela perturbe la largeur de ligne, de sorte que les éléments sont à peine visibles.
Gauthier
5

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, l'écran DPI doit être trouvé en premier, ce qui peut être fait ici par exemple: http://dpi.lv

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

Cyril
la source
2

L'OP souhaite conserver les données de pixels 1: 1. En tant qu'astronome travaillant avec des images scientifiques, je ne peux autoriser aucune interpolation des données d'image car cela introduirait un bruit ou des erreurs 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 à peu près 2x2, mais remarquez la colonne de 1x2 pixels

Vous pouvez voir que la plupart des pixels ont simplement été doublés (donc un pixel 1x1 devient 2x2) mais 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'indique Alka, plt.imsave () qui réalisera ce que l'OP demande. Supposons que vous ayez des données d'image stockées dans le 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 quand même spécifier le format avec format = 'png' pour autant que je sache), le tableau d'image est arr, et nous avons choisi l'échelle de gris inversée "gray_r" comme la palette de couleurs. J'ajoute généralement vmin et vmax pour spécifier la plage dynamique, mais ceux-ci sont facultatifs.

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

Remarque: l'OP n'a spécifié aucun axe, etc., ce que fait exactement cette solution. Si l'on veut ajouter des axes, des graduations, etc., mon approche préférée est de le faire sur un tracé séparé, en enregistrant avec transparent = True (PNG ou PDF) puis en superposant ce dernier sur l'image. Cela garantit que vous avez conservé les pixels d'origine intacts.

Colin Orion Chandler
la source
Merci. Cela fait quelques années que j'ai posé cette question, mais d'après ce que je me souviens et vois dans votre réponse, cela fonctionnerait bien lorsque vous essayez de sauvegarder un tableau de données sous forme d'image, mais que faire si vous voulez enregistrer est un réel figure elle - même (quel que soit son contenu) et contrôle toujours exactement les dimensions en pixels du fichier image résultant?
Amelio Vazquez-Reina le
-1

plt.imsave a travaillé pour moi. Vous pouvez trouver la documentation ici: 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
la source
Veuillez ajouter un exemple de code indiquant exactement le paramètre que vous définissez et les valeurs recommandées pour le cas d'utilisation de l'article d'origine.
Sarah Messer