Matplotlib - export figure to png in memory buffer - python

Is it possible to export a matplotlib figure as a png to a bytes type?
Here is the code I currently have:
def import_chart(df, x_label, y_label, title):
fig, ax = plt.subplots()
ax.plot(data[x_label], data[y_label])
ax.set(xlabel=x_label, ylabel=y_label,
title=title)
image_name = 'test.png'
fig.savefig(image_name)
f = open(image_name, 'rb+')
img = f.read()
f.close()
os.remove(image_name)
return img
The image that is returned is of type class 'bytes'. I would like to avoid saving to the hard drive and reading it again. Something like this:
def import_chart(self, x_label, y_label, title):
fig, ax = plt.subplots()
data = self.file_data.get_column([x_label,y_label])
ax.plot(data[x_label], data[y_label])
ax.set(xlabel=x_label, ylabel=y_label,
title=title)
buffer = buffer.to_buffer(fig.savefig(), format='png')
img = buffer.read()
return img

I've been using this to render matplotlib images from a web server:
import base64
from io import BytesIO
...
buffer = BytesIO()
plt.savefig(buffer, format='png')
buffer.seek(0)
image_png = buffer.getvalue()
buffer.close()
graphic = base64.b64encode(image_png)
graphic = graphic.decode('utf-8')
return graphic

Related

Can't convert object of type 'JpegImageFile' to 'str' for 'filename'

from PIL import Image
import numpy as np
def save(self, *args, **kwargs):
# open image
pil_img = Image.open(self.pic)
# convert the image to array and do some processing
cv_img = np.array(pil_img)
img = main(pil_img)
# convert back to pil image
im_pil = Image.fromarray(img)
# save
buffer = BytesIO()
im_pil.save(buffer, format='png')
image_png = buffer.getvalue()
self.pic.save(str(self.pic), ContentFile(image_png), save=False)
super().save(*args, **kwargs)
can't understand what's wrong with it.
i even tried to comment the cv_img = np.array(pil_img) but it still fail to run

Model card fails to show plots

I am trying to add a .png image to my model card. But it keeps giving a blank picture. I followed the instruction given by Google.
Here is what my code looks like,
from io import BytesIO
import base64
from google.colab import files
def plot_to_str():
img = BytesIO()
plt.savefig(img, format='png')
return base64.encodebytes(img.getvalue()).decode('utf-8')
my_plot = files.upload()
my_plot = plot_to_str()
And for the model card:
mct = mctlib.ModelCardToolkit()
model_card = mct.scaffold_assets()
# skipped the beginning of the model_card
model_card.model_parameters.data.append(mctlib.Dataset())
model_card.model_parameters.data[0].graphics.collection = [
mctlib.Graphic(image=my_plot)
]
mct.update_model_card(model_card)
html = mct.export_format()
display.display(display.HTML(html))
As a result I get this, a blan plot:

PDF data stream with python

Context: My code fetches a set of coordinates from some png documents and later on performs some redaction in certain fields (it uses these coordinates for drawing rectangles in certain areas).
I want my final output to be a pdf with each redacted image as page. I can achieve this with fpdf package with no problem.
However, I intend to send this pdf file as email (base64 encoded) attachment. Is there any way to get the base64 string from fpdf output?
On top of that, can I use image binary string in fpdf image method?
See the redact_pdf method below (I placed some comments there to be more clear)
Code:
class Redaction:
def __init__(self,png_image_list,df_coordinates):
self.png_image_list = png_image_list
self.df_coordinates = df_coordinates
def _redact_images(self):
redacted_images_bin = []
for page_num,page_data in enumerate(self.png_image_list):
im_page = Image.open(io.BytesIO(page_data))
draw = ImageDraw.Draw(im_page)
df_filtered = self.df_coordinates[self.df_coordinates['page_number'] == page_num+1]
for index, row in df_filtered.iterrows():
x0 = row['x0'] * im_page.size[0]
y0 = row['y0'] * im_page.size[1]
x1 = row['x1'] * im_page.size[0]
y1 = row['y1'] * im_page.size[1]
x2 = row['x2'] * im_page.size[0]
y2 = row['y2'] * im_page.size[1]
x3 = row['x3'] * im_page.size[0]
y3 = row['y3'] * im_page.size[1]
coords = [x0,y0,x1,y1,x2,y2,x3,y3]
draw.polygon(coords,outline='blue',fill='yellow')
redacted_images_bin.append(im_page)
return redacted_images_bin
def redacted_pdf(self):
redacted_images = self._redact_images()
pdf = FPDF()
pdf.set_auto_page_break(0)
for index,img_redacted in enumerate(redacted_images):
img_redacted.save(f"image_{index}.png")
pdf.add_page()
pdf.image(f"image_{index}.png",w=210,h=297)
os.remove(f"image_{index}.png") # I would like to avoid file handling!
pdf.output("doc.pdf","F") # I would like to avoid file handling!
#return pdf #this is what I want, to return the pdf as base64 or binary
In documentation I found that you can get PDF as string using
pdf_string = pdf.output(dest='S')
so you can use standard module base64
import fpdf
import base64
pdf = fpdf.FPDF()
# ... add some elements ...
pdf_string = pdf.output(dest='S')
pdf_bytes = pdf_string.encode('utf-8')
base64_bytes = base64.b64encode(pdf_bytes)
base64_string = base64_bytes.decode('utf-8')
print(base64_string)
Result:
JVBERi0xLjMKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL1Jlc291cmNlcyAyIDAgUgovQ29udGVudHMgNCAwIFI+PgplbmRvYmoKNCAwIG9iago8PC9GaWx0ZXIgL0ZsYXRlRGVjb2RlIC9MZW5ndGggMTk+PgpzdHJlYW0KeMKcM1LDsMOiMsOQMzVXKMOnAgALw7wCEgplbmRzdHJlYW0KZW5kb2JqCjEgMCBvYmoKPDwvVHlwZSAvUGFnZXMKL0tpZHMgWzMgMCBSIF0KL0NvdW50IDEKL01lZGlhQm94IFswIDAgNTk1LjI4IDg0MS44OV0KPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1Byb2NTZXQgWy9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUldCi9Gb250IDw8Cj4+Ci9YT2JqZWN0IDw8Cj4+Cj4+CmVuZG9iago1IDAgb2JqCjw8Ci9Qcm9kdWNlciAoUHlGUERGIDEuNy4yIGh0dHA6Ly9weWZwZGYuZ29vZ2xlY29kZS5jb20vKQovQ3JlYXRpb25EYXRlIChEOjIwMjIwMjE3MjExMDE3KQo+PgplbmRvYmoKNiAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMSAwIFIKL09wZW5BY3Rpb24gWzMgMCBSIC9GaXRIIG51bGxdCi9QYWdlTGF5b3V0IC9PbmVDb2x1bW4KPj4KZW5kb2JqCnhyZWYKMCA3CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDE3NSAwMDAwMCBuIAowMDAwMDAwMjYyIDAwMDAwIG4gCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA4NyAwMDAwMCBuIAowMDAwMDAwMzU2IDAwMDAwIG4gCjAwMDAwMDA0NjUgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSA3Ci9Sb290IDYgMCBSCi9JbmZvIDUgMCBSCj4+CnN0YXJ0eHJlZgo1NjgKJSVFT0YK
As for image(): it needs filename (or url) and it can't work with string or io.BytesIO().
Eventually you may get source code and you can try to change it.
There is even request on GitHub: Support for StringIO objects as images
EDIT:
I found that there is fork fpdf2 which can use pillow.Image in image() - see fpdf2 Image
And in source code I found image() can also work with io.BytesIO()
Example code for fpdf2 (output() gives bytes instead of string)
import fpdf
import base64
from PIL import Image
import io
#print(fpdf.__version__)
pdf = fpdf.FPDF()
pdf.add_page()
pdf.image('lenna.png')
pdf.image('https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png')
f = open('lenna.png', 'rb')
pdf.image(f)
f = Image.open('lenna.png')
pdf.image(f)
f = open('lenna.png', 'rb')
b = io.BytesIO()
b.write(f.read())
pdf.image(b)
# save in file
pdf.output('output.pdf')
# get as bytes
pdf_bytes = pdf.output()
#print(pdf_bytes)
base64_bytes = base64.b64encode(pdf_bytes)
base64_string = base64_bytes.decode('utf-8')
print(base64_string)
Wikipedia: Lenna [image]
Test for writing in fpdf2
import fpdf
pdf = fpdf.FPDF()
pdf.add_page()
pdf.image('https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png')
# --- test 1 ---
pdf.output('output-test-1.pdf')
# --- test 2 ---
pdf_bytes = pdf.output()
with open('output-test-2.pdf', 'wb') as f: # it will close automatically
f.write(pdf_bytes)
# --- test 2 ---
pdf_bytes = pdf.output()
f = open('output-test-3.pdf', 'wb')
f.write(pdf_bytes)
f.close() # don't forget to close when you write

Matplotlib graphic image to base64

Problem : Need to transform a graphic image of matplotlib to a base64 image
Current Solution : Save the matplot image in a cache folder and read it with read() method and then convert to base64
New Problem : Annoyance : Need a workaround so I dont need to save the graphic as image in any folder. I want to just use the image in the memory. Doing unnecessary I/O is a bad practice.
def save_single_graphic_data(data, y_label="Loss", x_label="Epochs", save_as="data.png"):
total_epochs = len(data)
plt.figure()
plt.clf()
plt.plot(total_epochs, data)
ax = plt.gca()
ax.ticklabel_format(useOffset=False)
plt.ylabel(y_label)
plt.xlabel(x_label)
if save_as is not None:
plt.savefig(save_as)
plt.savefig("cache/cached1.png")
cached_img = open("cache/cached1.png")
cached_img_b64 = base64.b64encode(cached_img.read())
os.remove("cache/cached1.png")
return cached_img_b64
import cStringIO
my_stringIObytes = cStringIO.StringIO()
plt.savefig(my_stringIObytes, format='jpg')
my_stringIObytes.seek(0)
my_base64_jpgData = base64.b64encode(my_stringIObytes.read())
[edit] in python3 it should be
import io
my_stringIObytes = io.BytesIO()
plt.savefig(my_stringIObytes, format='jpg')
my_stringIObytes.seek(0)
my_base64_jpgData = base64.b64encode(my_stringIObytes.read()).decode()
I think at least ... based on the documentation http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.savefig
For python 3
import base64
import io
pic_IObytes = io.BytesIO()
plt.savefig(pic_IObytes, format='png')
pic_IObytes.seek(0)
pic_hash = base64.b64encode(pic_IObytes.read())
The reason is both cStringIO and cStringIO.StringIO() are deprecated
I could not get answers above work, but this did:
import io
import base64
s = io.BytesIO()
plt.plot(list(range(100)))
plt.savefig(s, format='png', bbox_inches="tight")
plt.close()
s = base64.b64encode(s.getvalue()).decode("utf-8").replace("\n", "")
return '<img align="left" src="data:image/png;base64,%s">' % s

How to store an image in a variable

I would like to store the image generated by matplotlib in a variable raw_data to use it as inline image.
import os
import sys
os.environ['MPLCONFIGDIR'] = '/tmp/'
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
print "Content-type: image/png\n"
plt.plot(range(10, 20))
raw_data = plt.show()
if raw_data:
uri = 'data:image/png;base64,' + urllib.quote(base64.b64encode(raw_data))
print '<img src = "%s"/>' % uri
else:
print "No data"
#plt.savefig(sys.stdout, format='png')
None of the functions suit my use case:
plt.savefig(sys.stdout, format='png') - Writes it to stdout. This does help.. as I have to embed the image in a html file.
plt.show() / plt.draw() does nothing when executed from command line
Have you tried cStringIO or an equivalent?
import os
import sys
import matplotlib
import matplotlib.pyplot as plt
import StringIO
import urllib, base64
plt.plot(range(10, 20))
fig = plt.gcf()
imgdata = StringIO.StringIO()
fig.savefig(imgdata, format='png')
imgdata.seek(0) # rewind the data
print "Content-type: image/png\n"
uri = 'data:image/png;base64,' + urllib.quote(base64.b64encode(imgdata.buf))
print '<img src = "%s"/>' % uri
Complete python 3 version, putting together Paul's answer and metaperture's comment.
import matplotlib.pyplot as plt
import io
import urllib, base64
plt.plot(range(10))
fig = plt.gcf()
buf = io.BytesIO()
fig.savefig(buf, format='png')
buf.seek(0)
string = base64.b64encode(buf.read())
uri = 'data:image/png;base64,' + urllib.parse.quote(string)
html = '<img src = "%s"/>' % uri

Categories