Динамическая подача изображения matplotlib в Интернет с использованием Python

Этот вопрос был задан аналогичным образом, но ответ был у меня над головой (я новичок в разработке на Python и в веб-разработке), поэтому я надеюсь, что есть более простой способ, или его можно объяснить по-другому.

Я пытаюсь сгенерировать изображение с помощью 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, который я уже выводил.

6 ответов

Решение

Вам следует

  • сначала записать в объект cStringIO
  • затем напишите заголовок HTTP
  • затем запишите содержимое cStringIO в стандартный вывод

Таким образом, если ошибка в 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()

Приведенные выше ответы немного устарели - вот что у меня работает на 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())

Что работает для меня с python3:

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

Мой первый вопрос: часто ли меняется изображение? Вы хотите сохранить старые? Если это в режиме реального времени, то ваши поиски оптимизации оправданы. В противном случае, преимущества от создания изображения на лету не так уж значительны.

Код в его нынешнем виде потребует 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.

НТН,

Если только я не неправильно понял ваш вопрос, все, что вам нужно сделать, это перейти к расположению образа и запустить: python -m SimpleHTTPServer 8000 &

Затем откройте браузер и введите http://localhost:8000/ в строке URL.

Я знаю, что немного опоздал на вечеринку, но у меня возникла та же проблема, и в итоге я написал небольшой сценарий ниже.

Этот код Python 3.6+:

  • Запускает веб-сервер и сообщает, где его просмотреть
  • Сканирует себя на наличие методов класса, начинающихся с 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)
Другие вопросы по тегам