使用python动态地将matplotlib图像提供给Web

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

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

你需要告诉savefig在哪里写输出。你可以做到:

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个请求:

  • 获取您已经拥有的HTML源代码,以及
  • 获取实际图像
  • 可能最简单的方法(将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栏中键入http://localhost:8000/


    我知道我参加聚会有点晚了,但我也遇到了同样的问题,最后还是写了下面的小剧本。

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