Dynamically serving a matplotlib image to the web using python
这个问题在这里也是以一种类似的方式被问到的,但是我的答案是完全超出了我的想象(我对Python和Web开发非常陌生),所以我希望有一种更简单的方法,或者可以用不同的方式来解释。
我正在尝试使用matplotlib生成一个图像,并在不首先向服务器写入文件的情况下为其提供服务。我的代码可能有点傻,但它是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 " 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对象,然后执行如下操作:
1 2 | mybuffer=cStringIO.StringIO() pyplot.savefig(mybuffer, format="png") |
但我在那里迷路了。我所看到的所有例子(例如http://lost-theory.org/python/dynamicimg.html)都涉及到做类似的事情
1 2 | print"Content-type: image/png " |
我不知道如何将它与我已经输出的HTML集成在一起。
你应该
- 首先写入CStringIO对象
- 然后写入HTTP头
- 然后将cstringio的内容写入stdout
因此,如果发生
你需要告诉
1 2 3 4 5 6 7 | format ="png" sio = cStringIO.StringIO() pyplot.savefig(sio, format=format) print"Content-Type: image/%s " % format msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) # Needed this on windows, IIS sys.stdout.write(sio.getvalue()) |
如果要将图像嵌入到HTML中:
1 2 3 4 5 6 7 | print"Content-Type: text/html " 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() |
上面的答案有点过时——下面是我在python3+上得到图形数据的原始字节的方法。
1 2 3 4 5 6 | 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",并写出字节。
根据您用作Web服务器的内容,代码可能会有所不同。我使用Tornado作为我的Web服务器,代码是:
1 2 | self.set_header('Content-Type', 'image/png') self.write(figdata.getvalue()) |
我的第一个问题是:图像经常改变吗?你想保留旧的吗?如果它是实时的,那么你对优化的追求是合理的。否则,即时生成图像的好处就不那么重要了。
目前的代码需要2个请求:
可能最简单的方法(将Web请求保持在最低限度)是@alex l's comment,它允许您在一个请求中通过构建一个嵌入图像的HTML来完成这项工作。
您的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 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 " 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
我对python3的作用是:
1 2 3 4 5 | buf = io.BytesIO() plt.savefig(buf, format='png') image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8').replace(' ', '') buf.close() |
除非我严重误解了您的问题,否则您所需要做的就是CD到映像的位置并运行:python-m simplehttpserver 8000&;
然后打开浏览器,在URL栏中键入
我知道我参加聚会有点晚了,但我也遇到了同样的问题,最后还是写了下面的小剧本。
此python 3.6+代码:
- 启动Web服务器并告诉您在哪里查看它
- 扫描自身以"plot_uu"开头的类方法,并向浏览器提供绘图列表
- 对于单击的绘图,提示输入所需参数(如果有),包括自动刷新周期(秒)
- 执行绘图并刷新
正如您从代码中可以看出的那样,对于临时诊断和监视(在我的例子中是关于机器学习进度的)来说,它是故意最小化的。
您可能需要安装任何依赖项(plac+绘图所需的任何其他lib,例如i use pandas、matplotlib)
您可以通过双击(无参数)或命令行(有/无参数)运行文件。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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'{member[5:].replace("_","").title()}<br/> ' html = f'''<html><body>Available Plots{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 /> " html = f"<html><body>Parameters:<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>Error:{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) |