使用 python 向网络动态提供 matplotlib 图像

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14824522/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 12:33:44  来源:igfitidea点击:

Dynamically serving a matplotlib image to the web using python

pythonmatplotlibcgi

提问by Ben S.

This question has been asked in a similar way herebut the answer was way over my head (I'm super new to python and web development) so I'm hoping there's a simpler way or it could be explained differently.

这个问题在这里以类似的方式被问到,但答案超出了我的脑海(我对 python 和 web 开发非常陌生)所以我希望有一种更简单的方法,或者可以有不同的解释。

I'm trying to generate an image using matplotlib and serve it without first writing a file to the server. My code is probably kind of silly, but it goes like this:

我正在尝试使用 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>
"""

I think that instead of doing pyplot.savefig('test.png'), I am supposed to create a cstringIO object and then do something like this:

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

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

But I am pretty lost from there. All the examples I've seen (e.g. http://lost-theory.org/python/dynamicimg.html) involve doing something like

但我很迷茫。我见过的所有例子(例如http://lost-theory.org/python/dynamicimg.html)都涉及做类似的事情

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

and I don't get how to integrate that with the HTML I'm already outputting.

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

采纳答案by Thorsten Kranz

You should

你应该

  • first write to a cStringIO object
  • then write the HTTP header
  • then write the content of the cStringIO to stdout
  • 首先写入 cStringIO 对象
  • 然后写入 HTTP 标头
  • 然后将 cStringIO 的内容写入 stdout

Thus, if an error in savefigoccured, you could still return something else, even another header. Some errors won't be recognized earlier, e.g., some problems with texts, too large image dimensions etc.

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

You need to tell savefigwhere to write the output. You can do:

你需要告诉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())

If you want to embed the image into HTML:

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

回答by Alptigin Jalayr

Unless I badly miscomprehend your question, all you need to do is cd to the location of the image and run: python -m SimpleHTTPServer 8000 &

除非我严重误解你的问题,否则你需要做的就是 cd 到图像的位置并运行:python -m SimpleHTTPServer 8000 &

Then open your browser, and type http://localhost:8000/in the URL bar.

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

回答by Laur Ivan

My first question is: Does the image change often? Do you want to keep the older ones? If it's a real-time thing, then your quest for optimisation is justified. Otherwise, the benefits from generating the image on the fly aren't that significant.

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

The code as it stands would require 2 requests:

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

  1. to get the html source you already have and
  2. to get the actual image
  1. 获取您已经拥有的 html 源代码和
  2. 获取实际图像

Probably the simplest way (keeping the web requests to a minimum) is @Alex L's comment, which would allow you to do it in a single request, by building a HTML with the image embedded in it.

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

Your code would be something like:

您的代码将类似于:

# 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

This code will probably not work out of the box, but shows the idea.

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

Note that this is a bad idea in general if your image doesn't really change too often or generating it takes a long time, because it willbe generated every time.

请注意,如果您的图像不是经常更改或生成它需要很长时间,那么这通常是一个坏主意,因为它每次都会生成。

Another way would be to generate the original html. Loading it will trigger a request for the "test.png". You can serve that separately, either via the buffer streaming solution you already mention, or from a static file.

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

Personally, I'd stick with a decoupled solution: generate the image by another process (making sure that there's always an image available) and use a very light thing to generate and serve the HTML.

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

HTH,

哈,

回答by Mike N

The above answers are a little outdated -- here's what works for me on Python3+ to get the raw bytes of the figure data.

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

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

As mentioned in other answers you now need to set a 'Content-Type' header to 'image/png' and write out the bytes.

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

Depending on what you are using as your webserver the code may vary. I use Tornado as my webserver and the code to do that is:

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

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

回答by trulio

what works for me with python3 is:

python3对我有用的是:

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

回答by QA Collective

I know I'm a bit late to the party here, but I had this same problem and ended up with the small script below.

我知道我参加聚会有点晚了,但我遇到了同样的问题,最终得到了下面的小脚本。

This python 3.6+ code:

这个python 3.6+代码:

  • Starts a web server and tells you where to view it
  • Scans itself for class methods beginning with 'plot_' and provides the browser with a list of plots
  • For a clicked plot, prompts for required parameters (if any), including an automatic refresh period (in seconds)
  • Executes the plot and refreshes
  • 启动 Web 服务器并告诉您在哪里查看它
  • 扫描以“plot_”开头的类方法,并为浏览器提供绘图列表
  • 对于单击的绘图,提示输入所需的参数(如果有),包括自动刷新周期(以秒为单位)
  • 执行绘图并刷新

As you can tell by the code, it is deliberately minimal for temporary diagnostics and monitoring (of machine learning progress in my case).

从代码中可以看出,临时诊断和监视(在我的情况下是机器学习进度)的临时诊断和监视是故意最小的。

You may need to install any dependencies (plac + any other libs needed for plotting e.g. I use pandas, matplotlib)

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

You can run the file via double click (no parameters) or command line (with/without parameters)

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

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