web-dev-qa-db-fra.com

Service dynamique d'une image matplotlib sur le Web à l'aide de python

Cette question a été posée de la même manière ici mais la réponse était évidente (je ne connais que très peu le développement python et Web). J'espère donc qu'il existe un moyen plus simple ou qu'il pourrait être expliqué différemment.

J'essaie de générer une image à l'aide de matplotlib et de la servir sans écrire au préalable un fichier sur le serveur. Mon code est probablement un peu idiot, mais il va comme ceci:

import cgi
import matplotlib.pyplot as pyplot
import cStringIO #I think I will need this but not sure how to use

...a bunch of matplotlib stuff happens....
pyplot.savefig('test.png')

print "Content-type: text/html\n"
print """<html><body>
...a bunch of text and html here...
<img src="test.png"></img>
...more text and html...
</body></html>
"""

Je pense qu'au lieu de faire pyplot.savefig ('test.png'), je suis supposé créer un objet cstringIO puis faire quelque chose comme ça:

mybuffer=cStringIO.StringIO()
pyplot.savefig(mybuffer, format="png")

Mais je suis assez perdu à partir de là. Tous les exemples que j'ai vus (par exemple http://lost-theory.org/python/dynamicimg.html ) impliquent de faire quelque chose comme:

print "Content-type: image/png\n"

et je ne comprends pas comment intégrer cela avec le HTML que je produis déjà.

18
Ben S.

Vous devriez

  • d'abord écrire dans un objet cStringIO
  • puis écrivez l'en-tête HTTP
  • puis écrivez le contenu du cStringIO sur stdout

Ainsi, si une erreur dans savefig se produit, vous pouvez toujours retourner quelque chose d'autre, même un autre en-tête. Certaines erreurs ne seront pas reconnues plus tôt, par exemple des problèmes de texte, des dimensions d'image trop grandes, etc.

Vous devez indiquer savefig où écrire le résultat. Tu peux faire:

format = "png"
sio = cStringIO.StringIO()
pyplot.savefig(sio, format=format)
print "Content-Type: image/%s\n" % format
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) # Needed this on windows, IIS
sys.stdout.write(sio.getvalue())

Si vous souhaitez incorporer l'image au format HTML:

print "Content-Type: text/html\n"
print """<html><body>
...a bunch of text and html here...
<img src="data:image/png;base64,%s"/>
...more text and html...
</body></html>""" % sio.getvalue().encode("base64").strip()
18
Thorsten Kranz

Ma première question est la suivante: l'image change-t-elle souvent? Voulez-vous garder les plus âgés? Si c'est une opération en temps réel, votre quête d'optimisation est justifiée. Sinon, les avantages de la génération de l'image à la volée ne sont pas si importants.

Le code tel qu'il existe nécessiterait 2 demandes:

  1. pour obtenir la source HTML que vous avez déjà et
  2. pour obtenir l'image réelle

La manière la plus simple (en minimisant les requêtes sur le Web) est probablement le commentaire de @Alex L, ce qui vous permettrait de le faire en une seule requête, en créant un code HTML avec l'image incorporée.

Votre code serait quelque chose comme:

# Build your matplotlib image in a iostring here
# ......
#

# Initialise the base64 string
#
imgStr = "data:image/png;base64,"

imgStr += base64.b64encode(mybuffer)

print "Content-type: text/html\n"
print """<html><body>
# ...a bunch of text and html here...
    <img src="%s"></img>
#...more text and html...
    </body></html>
""" % imgStr

Ce code ne fonctionnera probablement pas immédiatement, mais montre l'idée.

Notez que ceci est une mauvaise idée en général si votre image ne change pas vraiment trop souvent ou si sa génération prend du temps, car sera sera généré à chaque fois.

Une autre façon serait de générer le code HTML d'origine. Le charger déclenchera une demande pour le "test.png". Vous pouvez le servir séparément, soit via la solution de diffusion en tampon que vous avez déjà mentionnée, soit à partir d'un fichier statique.

Personnellement, je m'en tenais à une solution découplée: générer l'image par un autre processus (en s'assurant qu'il y a toujours une image disponible) et utiliser un outil très léger pour générer et servir le code HTML.

HTH,

5
Laur Ivan

Les réponses ci-dessus sont un peu dépassées - voici ce qui fonctionne pour moi sur Python3 + pour obtenir les octets bruts des données de figure.

import matplotlib.pyplot as plt
from io import BytesIO
fig = plt.figure()
plt.plot(range(10))
figdata = BytesIO()
fig.savefig(figdata, format='png')

Comme mentionné dans d'autres réponses, vous devez maintenant définir un en-tête "Content-Type" pour "image/png" et rédiger les octets.

En fonction de ce que vous utilisez comme serveur Web, le code peut varier. J'utilise Tornado comme serveur Web et le code pour le faire est le suivant:

self.set_header('Content-Type', 'image/png')
self.write(figdata.getvalue())
5
Mike N

Sauf si je comprends mal votre question, tout ce que vous avez à faire est de vous connecter à l’emplacement de l’image et de lancer: python -m SimpleHTTPServer 8000 &

Puis ouvrez votre navigateur et tapez http://localhost:8000/ dans la barre d’URL.

2
Alptigin Jalayr

ce qui fonctionne pour moi avec python3 est:

buf = io.BytesIO()
plt.savefig(buf, format='png')
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8').replace('\n', '')
buf.close()
2
trulio

Je sais que je suis un peu en retard à la fête ici, mais j'ai eu le même problème et j'ai fini avec le petit script ci-dessous.

Ce code python 3.6+:

  • Démarre un serveur Web et vous indique où le visualiser.
  • Scanne lui-même les méthodes de classe commençant par 'plot_' et fournit au navigateur une liste de tracés.
  • Pour un tracé sélectionné, demande les paramètres requis (le cas échéant), y compris une période d'actualisation automatique (en secondes).
  • Exécute l'intrigue et rafraîchit

Comme vous pouvez le constater par le code, il est délibérément minimal pour le diagnostic et la surveillance temporaires (des progrès de l’apprentissage automatique dans mon cas).

Vous devrez peut-être installer des dépendances (plac + toutes les autres bibliothèques nécessaires pour le traçage, par exemple, j'utilise des pandas, matplotlib)

Vous pouvez exécuter le fichier par double clic (pas de paramètre) ou en ligne de commande (avec/sans paramètre)

Code:

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import io
from http.server import HTTPServer,BaseHTTPRequestHandler
import urllib
import inspect


class PlotRequestHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        args = urllib.parse.parse_qs(self.path[2:])
        args = {i:args[i][0] for i in args}
        html = ''

        if 'mode' not in args:
            plots = ''
            for member in dir(self):
                if member[:5] == 'plot_':
                    plots += f'<a href="http://{self.server.server_name}:{self.server.server_port}/?mode=paramcheck&graph={member}">{member[5:].replace("_"," ").title()}</a><br/>\n'
            html = f'''<html><body><h1>Available Plots</h1>{plots}</body></html>'''

        Elif args['mode'] == 'paramcheck':
            plotargs = inspect.getargspec(getattr(self,args['graph'])).args
            if len(plotargs) == 1 and plotargs[0].lower()=='self':
                args['mode'] = 'plotpage'
            else:
                for arg in plotargs:
                    if arg.lower() != 'self':
                        html += f"<input name='{arg}' placeholder='{arg}' value='' /><br />\n"
                html = f"<html><body><h1>Parameters:</h1><form method='GET'>{html}<input name='refresh_every' value='60' />(Refresh in sec)<br /><input type='hidden' name='mode' value='plotpage'/><input type='hidden' name='graph' value='{args['graph']}'/><input type='submit' value='Plot!'/></form></body></html>"

        if 'mode' in args and args['mode'] == 'plotpage':
            html = f'''<html><head><meta http-equiv="refresh" content="{args['refresh_every']};URL=\'http://{self.server.server_name}:{self.server.server_port}{self.path}\'" /></head>
                       <body><img src="http://{self.server.server_name}:{self.server.server_port}{self.path.replace('plotpage','plot')}" /></body>'''

        Elif 'mode' in args and args['mode'] == 'plot':
            try:
                plt = getattr(self,args['graph'])(*Tuple((args[arg] for arg in inspect.getargspec(getattr(self,args['graph'])).args if arg in args)))
                self.send_response(200)
                self.send_header('Content-type', 'image/png')
                self.end_headers()
                plt.savefig(self.wfile, format='png')
            except Exception as e:
                html = f"<html><body><h1>Error:</h1>{e}</body></html>"

        if html != '':
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(bytes(html,'utf-8'))

    def plot_convergence(self, file_path, sheet_name=None):
        if sheet_name == None:
            data = pd.read_csv(file_path)
        else:
            data = pd.read_Excel(file_path, sheet_name)

        fig, ax1 = plt.subplots()

        ax1.set_xlabel('Iteration')
        ax1.set_ylabel('LOSS', color='tab:red')
        ax1.set_ylim([0,1000])
        ax1.plot(data.iteration, data.loss, color='tab:red')

        ax2 = ax1.twinx()

        ax2.set_ylabel('Precision, Recall, f Score')
        ax2.set_ylim([0,1])
        ax2.plot(data.iteration, data.precision, color='tab:blue')
        ax2.plot(data.iteration, data.recall, color='tab:green')
        ax2.plot(data.iteration, data['f-score'], color='tab:orange')

        fig.tight_layout()
        plt.legend(loc=6)
        return plt


def main(server_port:"Port to serve on."=9999,server_address:"Local server name."=''):
    httpd = HTTPServer((server_address, server_port), PlotRequestHandler)
    print(f'Serving on http://{httpd.server_name}:{httpd.server_port} ...')
    httpd.serve_forever()


if __== '__main__':
    import plac; plac.call(main)
0
QA Collective