【问题标题】:Dynamically serving a matplotlib image to the web using python使用 python 动态地将 matplotlib 图像提供给网络
【发布时间】:2013-01-27 06:51:10
【问题描述】:

已经以类似的方式提出了这个问题here,但答案超出了我的想象(我对 python 和 Web 开发非常陌生)所以我希望有一个更简单的方法或者可以用不同的方式解释.

我正在尝试使用 matplotlib 生成图像并提供它,而无需先将文件写入服务器。我的代码可能有点傻,但它是这样的:

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>
"""

我认为与其做 pyplot.savefig('test.png'),不如创建一个 cstringIO 对象,然后做这样的事情:

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

但是我从那里迷路了。我见过的所有例子(例如http://lost-theory.org/python/dynamicimg.html)都涉及到类似

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

我不知道如何将它与我已经输出的 HTML 集成。

【问题讨论】:

标签: python matplotlib cgi


【解决方案1】:

我知道我在这里聚会有点晚了,但我遇到了同样的问题,最后得到了下面的小脚本。

这个python 3.6+代码:

  • 启动 Web 服务器并告诉您在哪里查看它
  • 扫描自身以查找以“plot_”开头的类方法,并为浏览器提供绘图列表
  • 对于单击的绘图,提示输入所需参数(如果有),包括自动刷新周期(以秒为单位)
  • 执行绘图并刷新

从代码中可以看出,临时诊断和监控(在我的例子中是机器学习进度)故意将其最小化。

您可能需要安装任何依赖项(plac + 绘图所需的任何其他库,例如我使用 pandas、matplotlib)

您可以通过双击(无参数)或命令行(有/无参数)运行文件

代码:

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 __name__ == '__main__':
    import plac; plac.call(main)

【讨论】:

    【解决方案2】:

    上面的答案有点过时了——这就是我在 Python3+ 上获取图形数据的原始字节的方法。

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

    正如其他答案中提到的,您现在需要将“Content-Type”标头设置为“image/png”并写出字节。

    根据您使用的网络服务器,代码可能会有所不同。我使用 Tornado 作为我的网络服务器,代码如下:

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

    【讨论】:

    • 我无法获得正确的缓冲区内容长度。当我通过 ajax 下载图像时,我总是得到一个 ERR_CONTENT_LENGTH_MISMATCH 错误,我尝试了 sys.getsizeof(figdata)figdata.__sizeof__()。我最终根本没有指定内容长度。
    • @TomTom101 - 你的 HTTP 服务器使用什么库?你可能想要len(figdata.getvalue())(你得到的是 BytesIO 对象的大小,而不是实际的数据流)
    • 使用 nginx、Gunicorn 和 Falcon。我会试试你的建议
    【解决方案3】:

    python3 对我有用的是:

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

    【讨论】:

      【解决方案4】:

      你应该

      • 首先写入一个 cStringIO 对象
      • 然后写HTTP头
      • 然后将cStringIO的内容写入stdout

      因此,如果savefig 中发生错误,您仍然可以返回其他内容,甚至是另一个标题。一些错误不会更早地被识别出来,例如,文本的一些问题、图像尺寸过大等。

      您需要告诉savefig 在哪里写入输出。你可以这样做:

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

      如果要将图像嵌入 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()
      

      【讨论】:

        【解决方案5】:

        我的第一个问题是:图像是否经常变化?你想保留旧的吗?如果它是实时的,那么您对优化的追求是合理的。否则,动态生成图像的好处就不那么明显了。

        目前的代码需要 2 个请求:

        1. 获取您已有的 html 源代码并
        2. 获取实际图像

        可能最简单的方法(将网络请求保持在最低限度)是@Alex L 的评论,它允许您通过构建嵌入图像的 HTML 在单个请求中完成。

        您的代码将类似于:

        # 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
        

        此代码可能无法开箱即用,但显示了想法。

        请注意,如果您的图像并没有真正频繁地更改或生成它需要很长时间,那么这通常是一个坏主意,因为它每次都会生成。

        另一种方法是生成原始 html。加载它将触发对“test.png”的请求。您可以通过您已经提到的缓冲区流解决方案或从静态文件单独提供它。

        就个人而言,我会坚持使用解耦的解决方案:通过另一个进程生成图像(确保始终有可用的图像)并使用非常轻量级的东西来生成和提供 HTML。

        HTH,

        【讨论】:

          【解决方案6】:

          除非我严重误解了您的问题,否则您只需 cd 到图像的位置并运行:python -m SimpleHTTPServer 8000 &

          然后打开浏览器,在 URL 栏中输入 http://localhost:8000/

          【讨论】:

          • 不是我要找的,但您提示我改写清楚,谢谢!
          猜你喜欢
          • 1970-01-01
          • 2018-04-04
          • 1970-01-01
          • 2020-05-06
          • 1970-01-01
          • 2013-06-18
          • 2010-11-07
          • 1970-01-01
          • 2013-07-14
          相关资源
          最近更新 更多