Générer un PNG avec matplotlib lorsque DISPLAY n'est pas défini

319

J'essaie d'utiliser networkx avec Python. Lorsque j'exécute ce programme, il obtient cette erreur. Y a-t-il quelque chose qui manque?

#!/usr/bin/env python

import networkx as nx
import matplotlib
import matplotlib.pyplot
import matplotlib.pyplot as plt

G=nx.Graph()
G.add_node(1)
G.add_nodes_from([2,3,4,5,6,7,8,9,10])
#nx.draw_graphviz(G)
#nx_write_dot(G, 'node.png')
nx.draw(G)
plt.savefig("/var/www/node.png")


Traceback (most recent call last):
  File "graph.py", line 13, in <module>
    nx.draw(G)
  File "/usr/lib/pymodules/python2.5/networkx/drawing/nx_pylab.py", line 124, in draw
    cf=pylab.gcf()
  File "/usr/lib/pymodules/python2.5/matplotlib/pyplot.py", line 276, in gcf
    return figure()
  File "/usr/lib/pymodules/python2.5/matplotlib/pyplot.py", line 254, in figure
    **kwargs)
  File "/usr/lib/pymodules/python2.5/matplotlib/backends/backend_tkagg.py", line 90, in new_figure_manager
    window = Tk.Tk()
  File "/usr/lib/python2.5/lib-tk/Tkinter.py", line 1650, in __init__
    self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: no display name and no $DISPLAY environment variable

J'ai maintenant une erreur différente:

#!/usr/bin/env python

import networkx as nx
import matplotlib
import matplotlib.pyplot
import matplotlib.pyplot as plt

matplotlib.use('Agg')

G=nx.Graph()
G.add_node(1)
G.add_nodes_from([2,3,4,5,6,7,8,9,10])
#nx.draw_graphviz(G)
#nx_write_dot(G, 'node.png')
nx.draw(G)
plt.savefig("/var/www/node.png")

/usr/lib/pymodules/python2.5/matplotlib/__init__.py:835: UserWarning:  This call to matplotlib.use() has no effect
because the the backend has already been chosen;
matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
or matplotlib.backends is imported for the first time.

  if warn: warnings.warn(_use_error_msg)
Traceback (most recent call last):
  File "graph.py", line 15, in <module>
    nx.draw(G)
  File "/usr/lib/python2.5/site-packages/networkx-1.2.dev-py2.5.egg/networkx/drawing/nx_pylab.py", line 124, in draw
    cf=pylab.gcf()
  File "/usr/lib/pymodules/python2.5/matplotlib/pyplot.py", line 276, in gcf
    return figure()
  File "/usr/lib/pymodules/python2.5/matplotlib/pyplot.py", line 254, in figure
    **kwargs)
  File "/usr/lib/pymodules/python2.5/matplotlib/backends/backend_tkagg.py", line 90, in new_figure_manager
    window = Tk.Tk()
  File "/usr/lib/python2.5/lib-tk/Tkinter.py", line 1650, in __init__
    self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: no display name and no $DISPLAY environment variable

J'ai maintenant une erreur différente:

#!/usr/bin/env python

import networkx as nx
import matplotlib
import matplotlib.pyplot
import matplotlib.pyplot as plt

matplotlib.use('Agg')

G=nx.Graph()
G.add_node(1)
G.add_nodes_from([2,3,4,5,6,7,8,9,10])
#nx.draw_graphviz(G)
#nx_write_dot(G, 'node.png')
nx.draw(G)
plt.savefig("/var/www/node.png")

/usr/lib/pymodules/python2.5/matplotlib/__init__.py:835: UserWarning:  This call to matplotlib.use() has no effect
because the the backend has already been chosen;
matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
or matplotlib.backends is imported for the first time.

  if warn: warnings.warn(_use_error_msg)
Traceback (most recent call last):
  File "graph.py", line 15, in <module>
    nx.draw(G)
  File "/usr/lib/python2.5/site-packages/networkx-1.2.dev-py2.5.egg/networkx/drawing/nx_pylab.py", line 124, in draw
    cf=pylab.gcf()
  File "/usr/lib/pymodules/python2.5/matplotlib/pyplot.py", line 276, in gcf
    return figure()
  File "/usr/lib/pymodules/python2.5/matplotlib/pyplot.py", line 254, in figure
    **kwargs)
  File "/usr/lib/pymodules/python2.5/matplotlib/backends/backend_tkagg.py", line 90, in new_figure_manager
    window = Tk.Tk()
  File "/usr/lib/python2.5/lib-tk/Tkinter.py", line 1650, in __init__
    self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: no display name and no $DISPLAY environment variable
krisdigitx
la source
3
duplication possible de la génération de graphiques matplotlib sans serveur X en cours d'exécution
Jouni K. Seppänen
9
Déplacez l'appel à matplotlib.use ('Agg') au-dessus de vos autres importations, en particulier il devrait être avant l'importation de matplotlib.pyplot
Ivo Bosticky
Le commentaire @IvoBosticky l'a également résolu: la seule chose trompeuse est "au-dessus de vos autres importations". Il devrait être évident que vous devez importer matplotlib avant ... C'est tout le paramètre qui a fonctionné pour moi: import matplotlib // matplotlib.use ('Agg') // import matplotlib.pyplot as plt
mrk

Réponses:

518

Le problème principal est que (sur votre système) matplotlib choisit un backend utilisant x par défaut. Je viens d'avoir le même problème sur l'un de mes serveurs. La solution pour moi était d'ajouter le code suivant dans un endroit qui est lu avant toute autre importation pylab / matplotlib / pyplot :

import matplotlib
# Force matplotlib to not use any Xwindows backend.
matplotlib.use('Agg')

L'alternative est de le placer dans votre .matplotlibrc

Reinout van Rees
la source
182
Remarque importante: .use doit être appelé avant l'importation de pyplot. Donc, si vous essayez, par exemple, d'importer pyplot, vous devez d'abord importer matplotlib, appeler use, puis importer pyplot.
seaotternerd
8
Le commentaire ci-dessus est expliqué davantage par cette réponse .
Ioannis Filippidis
2
Comment le "placez-vous dans votre .matplotlibrc"?
tommy.carstensen
18
backend: aggdans ~/.config/matplotlib'/matplotlibrc(à titre d'exemple, voirhttp: //matplotlib.org/faq/troubleshooting_faq.html#locating-matplotlib-config-dir). Voir aussi matplotlib.org/users/customizing.html , qui a un exemple de fichier de configuration au bas de la page. Trouvez "agg" sur cette page et vous verrez l'option de configuration dont vous avez besoin.
Reinout van Rees
4
Pour référence, voici le lien vers la documentation de matplotlib qui explique cela. (+1, excellente réponse, m'a parfaitement aidé!)
Tim S.
72

Tout comme un complément à la réponse de Reinout.

Le moyen permanent de résoudre ce type de problème est de modifier le fichier .matplotlibrc. Trouvez-le via

>>> import matplotlib
>>> matplotlib.matplotlib_fname() # This is the file location in Ubuntu '/etc/matplotlibrc'

Modifiez ensuite le backend de ce fichier en backend : Agg. C'est ça.

Chris.Q
la source
5
$MATPLOTLIBRCConseil de pro: définissez le répertoire dans lequel vous souhaitez jeter votre propre matplotlibrc.
Kenneth Hoste
Un peu exagéré pour un problème comme celui-ci, mais je suppose que si le serveur fonctionne toujours sans tête, il est logique de modifier un fichier de configuration. Cela aurait-il des effets secondaires sur le fonctionnement de matplotlib?
BruceJohnJennerLawso
J'utilise matplotlib sur un serveur Web, donc c'était la réponse pour moi. Je n'ai remarqué aucun effet secondaire.
spitz
42

La bonne réponse est de prendre un peu de temps pour préparer correctement votre environnement d'exécution.

La première technique que vous devez préparer votre environnement d'exécution consiste à utiliser un matplotlibrcfichier, comme recommandé judicieusement par Chris Q. , en définissant

backend : Agg

dans ce fichier. Vous pouvez même contrôler - sans changement de code - comment et où matplotlib recherche et trouve le matplotlibrcfichier .

La deuxième technique pour préparer votre environnement d'exécution est d'utiliser la MPLBACKENDvariable d'environnement (et d'informer vos utilisateurs de s'en servir):

export MPLBACKEND="agg"
python <program_using_matplotlib.py>

C'est pratique car vous n'avez même pas besoin de fournir un autre fichier sur le disque pour que cela fonctionne. J'ai utilisé cette approche avec, par exemple, des tests en intégration continue et en cours d'exécution sur des machines distantes qui n'ont pas d'écran.

Coder en dur votre backend matplotlib à "Agg" dans votre code Python, c'est comme frapper une cheville carrée dans un trou rond avec un gros marteau, quand, à la place, vous auriez pu dire à matplotlib qu'il doit être un trou carré.

gotgenes
la source
La deuxième technique ressemble à la plus élégante dans cette situation.
Dmitry Kabanov
L'utilisation de MPLBACKEND l'a résolu pour moi. Certainement la manière la plus élégante!
SaturnFromTitan
41

J'ai eu l'erreur lors de l'utilisation de matplotlib via Spark. matplotlib.use('Agg')ne fonctionne pas pour moi. En fin de compte, le code suivant fonctionne pour moi. Plus ici

import matplotlib.pyplot as plt.
plt.switch_backend('agg')
user3282611
la source
Cela fonctionne très bien, sans les restrictions sur l'ordre utilisé pour importer matplotlib et d'autres bibliothèques.
PabTorre
Lors de l'exécution sur Spark, avez-vous dû restreindre cela pour qu'il s'exécute sur le nœud principal ou avez-vous réussi à le faire fonctionner sur les nœuds de travail?
Saca
J'utilise ceci dans un projet django et c'était la seule façon de le faire fonctionner.
HenryM
31

Je vais simplement répéter ce que @Ivo Bosticky a dit, ce qui peut être ignoré. Mettez ces lignes au TRÈS début du fichier py.

import matplotlib
matplotlib.use('Agg') 

Ou on obtiendrait une erreur

* / usr / lib / pymodules / python2.7 / matplotlib / __ init__.py:923: UserWarning: Cet appel à matplotlib.use () n'a aucun effet
parce que le backend a déjà été choisi;
matplotlib.use () doit être appelé * avant * pylab, matplotlib.pyplot, *

Cela résoudra tous les problèmes d'affichage

Somum
la source
15

J'ai trouvé que cet extrait de code fonctionnait bien lors de la commutation entre les environnements X et non-X.

import os
import matplotlib as mpl
if os.environ.get('DISPLAY','') == '':
    print('no display found. Using non-interactive Agg backend')
    mpl.use('Agg')
import matplotlib.pyplot as plt
Matthias123
la source
À mon avis, c'est une solution supérieure à celle acceptée, même si elle ne répond pas directement à la question et répond à une question non posée.
Daisuke Aramaki
14

Lorsque vous vous connectez au serveur pour exécuter le code, utilisez plutôt ceci:

ssh -X username@servername

le -Xsupprimera le nom sans affichage et aucune erreur de variable d'environnement $ DISPLAY

:)

rajol kochlashvili
la source
1
J'ai besoin d'utiliser '-X' pour enregistrer l'image .png. Merci beaucoup.
nos
Cela échouera pendant un long processus si ssh expire ou si vous devez vous déconnecter pour une raison quelconque. Notez qu'un délai d'attente peut même se produire si le client qui se connecte se met en veille.
posdef
Vous pouvez empêcher les délais d'expiration en ajoutant -o ServerAliveCountMax=120 -o ServerAliveInterval=30ce qui fera que le client ssh enverra un paquet vide toutes les 30 secondes pendant 1 heure maximum.
Alex
5

Sur quel système êtes-vous? Il semble que vous ayez un système avec X11, mais la variable d'environnement DISPLAY n'a pas été correctement définie. Essayez d'exécuter la commande suivante, puis réexécutez votre programme:

export DISPLAY=localhost:0
Michael Aaron Safyan
la source
mais pourquoi faut-il définir une variable d'affichage, je suis connecté à distance à ce serveur, tout ce qu'il doit faire est de générer un fichier PNG ???
krisdigitx
1
@krisdigitx, si vous êtes connecté à distance, ne définissez pas de variable d'affichage; utilisez plutôt l'indicateur "-XY" lorsque vous vous connectez. Pour afficher, il doit savoir à quel serveur X envoyer l'image; dans ce cas, ce serait l'affichage de votre ordinateur, au lieu de l'ordinateur distant. L'utilisation de l'indicateur "-XY" fait que SSH définit automatiquement la variable DISPLAY pour pointer vers l'affichage de l'ordinateur qui se connecte.
Michael Aaron Safyan
@krisdigitx, je suis d'accord, c'est très étrange que ça fasse ça; ma conjecture, cependant, est qu'il peint l'image en utilisant X11, puis enregistre le résultat en utilisant X11.
Michael Aaron Safyan,
L'utilisation de ce paramètre pour $ DISPLAY ne fonctionne pas sur EC2 exécutant Ubuntu 16 - impossible de se connecter pour afficher "localhost: 0"
PabTorre
5
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

Ça marche pour moi.

Qing En
la source
3

Une autre chose à vérifier est de savoir si votre utilisateur actuel est autorisé à se connecter à l'écran X. Dans mon cas, root n'était pas autorisé à le faire et matplotlib se plaignait de la même erreur.

user@debian:~$ xauth list         
debian/unix:10  MIT-MAGIC-COOKIE-1  ae921efd0026c6fc9d62a8963acdcca0
root@debian:~# xauth add debian/unix:10  MIT-MAGIC-COOKIE-1 ae921efd0026c6fc9d62a8963acdcca0
root@debian:~# xterm

la source: http://www.debian-administration.org/articles/494 https://debian-administration.org/article/494/Getting_X11_forwarding_through_ssh_working_after_running_su

Alex
la source
2

Pour vous assurer que votre code est portable sur Windows, Linux et OSX et pour les systèmes avec et sans écrans, je suggère l'extrait de code suivant:

import matplotlib
import os
# must be before importing matplotlib.pyplot or pylab!
if os.name == 'posix' and "DISPLAY" not in os.environ:
    matplotlib.use('Agg')

# now import other things from matplotlib
import matplotlib.pyplot as plt

Crédit: https://stackoverflow.com/a/45756291/207661

Shital Shah
la source
1

Pour Google Cloud Machine Learning Engine:

import matplotlib as mpl
mpl.use('Agg')
from matplotlib.backends.backend_pdf import PdfPages

Et puis pour imprimer dans un fichier:

#PDF build and save
    def multi_page(filename, figs=None, dpi=200):
        pp = PdfPages(filename)
        if figs is None:
            figs = [mpl.pyplot.figure(n) for n in mpl.pyplot.get_fignums()]
        for fig in figs:
            fig.savefig(pp, format='pdf', bbox_inches='tight', fig_size=(10, 8))
        pp.close()

et pour créer le PDF:

multi_page(report_name)
Kim Miller
la source