Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document some pyodide matplotlib suggestions for node and deno #36

Open
rajsite opened this issue May 5, 2023 · 4 comments
Open

Document some pyodide matplotlib suggestions for node and deno #36

rajsite opened this issue May 5, 2023 · 4 comments

Comments

@rajsite
Copy link

rajsite commented May 5, 2023

The Node and Deno contexts can't use the matplotlib_pyodide backends but work fine with Agg.

Takes a second to piece together but one pattern I'm finding useful is to save the output as a data: url.

import base64
import io 
import numpy as np
import matplotlib
from matplotlib import pyplot as plt

matplotlib.use('Agg')

x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)

pic_IObytes = io.BytesIO()
plt.savefig(pic_IObytes, format='png')
pic_IObytes.seek(0)
pic_hash = base64.b64encode(pic_IObytes.read()).decode('utf-8')
dataurl = f'data:image/png;base64,{pic_hash}'
dataurl

Which seems to work! :D

Couldn't think of the right place for it but I think an example like that could be handy (even if just in this issue). Also interested in easier / more efficient patterns for sharing matplotlib figures between the python and JS contexts.

@rajsite
Copy link
Author

rajsite commented May 5, 2023

Ah lol it was right there the whole time

data = io.BytesIO()
try:
self.canvas.figure.savefig(data, format=format)
except Exception:
raise
element.setAttribute(
"href",
"data:{};base64,{}".format(
mimetype, base64.b64encode(data.getvalue()).decode("ascii")
),
)
element.setAttribute("download", f"plot.{format}")

@hoodmane
Copy link
Member

hoodmane commented May 5, 2023

Well I wouldn't necessarily recommend this if you can avoid it. I think the technique here:

pixels = self.buffer_rgba().tobytes()

Is more efficient than base64 encoding it (though creating a bytes object may still be an unnecessary copy).

It depends a bit on what you want to do with the image. Do you want to store it to the file system?

@rajsite
Copy link
Author

rajsite commented May 5, 2023

Ahh. Yea, just trying to get the byte array into the js context. Looks like the following is how to access the buffer with FigureCanvasAgg:

import matplotlib
from matplotlib import pyplot as plt
import numpy as np

matplotlib.use('Agg')

x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)

fig.canvas.draw()
buff = fig.canvas.get_renderer().buffer_rgba()

# For illustration
import js
js.window.buff = buff

# data is typed array view on wasm memory
# let data = window.buff.getBuffer('u8').data;

Seems much better than the data URL (assuming the raw buffer is useful)! Would it be zero copies up to this point? If so that's pretty fancy

@hoodmane
Copy link
Member

hoodmane commented May 5, 2023

You'll need to use pyodide.ffi.create_proxy but yes this should be zero copy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants