Open JackLilhammers opened 1 year ago
As mentioned in this issue, the only way to redirect the GR output is to use the GKSwstype
environment variable, e.g.
GKSwstype=svg python ...
Could you provide an example how you use GR as a backend for MPL? How did you patch the backend?
I'm sorry, here some code :)
I made a test script using this example as base: https://gr-framework.org/examples/figanim.html It's quite long, so I attached it last.
You can try GR as a backend for matplotlib and/or as backend for savefig()
If you run the script with -s gr
or with -b gr -o
it'll crash.
It'll crash with the Agg backend too, because it doesn't support SVGs.
You'll notice that if you select GR as backend it's not actually used, because FigureCanvasGR
doesn't have a print_svg()
method, and matplotlib falls back to its SVG backend.
Here's my dumb print_svg()
for FigureCanvasGR
def print_svg(self, filename, *args, **kwargs):
gr.beginprint(filename)
self.draw()
gr.endprint()
This however is as slow as the default one, and I don't know why. Maybe because the slow part runs in matplotlib
To see what's the actual backend used, I added a couple of debug prints at the beginning of _switch_canvas_and_return_print_method()
in backend_bases.py inside matplotlib
def _switch_canvas_and_return_print_method(self, fmt, backend=None):
"""..."""
canvas = None
if backend is not None:
print(f'using the selected backend: {backend}')
# Return a specific canvas class, if requested.
canvas_class = (
importlib.import_module(cbook._backend_module_name(backend))
.FigureCanvas)
if not hasattr(canvas_class, f"print_{fmt}"):
raise ValueError(
f"The {backend!r} backend does not support {fmt} output")
elif hasattr(self, f"print_{fmt}"):
print(f'using current backend: {self.__class__}')
# Return the current canvas if it supports the requested format.
canvas = self
canvas_class = None # Skip call to switch_backends.
else:
# Return a default canvas for the requested format, if it exists.
canvas_class = get_registered_canvas_class(fmt)
print(f'selected the default backend: {canvas_class}')
Here's your modified example
import sys
import os
from timeit import default_timer as timer
import numpy as np
import os
import shutil
import argparse
# env
os.environ["GKS_WSTYPE"] = 'svg'
# utility function because gr is easier to write
def backend_name(name: str|None) -> str|None:
if name is not None:
return name.lower() if name.lower() != 'gr' else 'module://gr.matplotlib.backend_gr'
#-------------------------------------------------------------------------------
# args
parser = argparse.ArgumentParser()
parser.add_argument(
"-b", "--backend",
help="Selects a backend for matplotlib",
)
parser.add_argument(
"-s", "--savefig",
help="Selects a backend for savefig(), can be overridden by `--override`",
)
parser.add_argument(
"-o", "--override",
help="The backend selected with `--backend`, if any, will be used for savefig(), "
"otherwise it'll use the default one. "
"If `--savefig` is passed it'll be ignored",
action="store_true",
)
args = parser.parse_args()
backend = backend_name(args.backend)
if backend is not None:
os.environ["MPLBACKEND"] = backend
savefig = backend_name(args.savefig)
override = bool(args.override)
#-------------------------------------------------------------------------------
# output folder
PLOTS_PATH = 'plots'
shutil.rmtree(PLOTS_PATH, ignore_errors=True)
os.mkdir(PLOTS_PATH)
os.chdir(PLOTS_PATH)
#-------------------------------------------------------------------------------
x = np.arange(0, 2 * np.pi, 0.01)
# create an animation using GR
from gr.pygr import plot
tstart = timer()
for i in range(1, 100):
plot(x, np.sin(x + i / 10.0))
if i % 2 == 0:
print('.', end="")
sys.stdout.flush()
fps_gr = int(100 / (timer() - tstart))
print('fps (GR): %4d' % fps_gr)
# create the same animation using matplotlib
import matplotlib
import matplotlib.pyplot as plt
# the current backend, just to be sure
print(f'matplotlib is using: {matplotlib.get_backend()}')
# choose a backend for savefig
# if is overridden uses backend
if not override:
if savefig is not None:
# user selected
backend = savefig
else:
# default
backend = None
tstart = timer()
for i in range(1, 100):
plt.clf()
plt.plot(np.sin(x + i / 10.0))
plt.savefig(f'mpl{i:04d}.svg', backend=backend)
if i % 2 == 0:
print('.', end="")
sys.stdout.flush()
fps_mpl = int(100 / (timer() - tstart))
print('fps (mpl): %4d' % fps_mpl)
print(' speedup: %6.1f' % (float(fps_gr) / fps_mpl))
The plain GR example works fine:
from timeit import default_timer as timer
import numpy as np
from gr.pygr.mlab import plot, savefig
x = np.arange(0, 2 * np.pi, 0.01)
tstart = timer()
for i in range(100):
plot(x, np.sin(x + i / 10.0))
savefig(f'gr{i:04d}.svg')
fps_gr = int(100 / (timer() - tstart))
print('fps (gr): %4d' % fps_gr)
Therefore I have to assume that something is suboptimally implemented in Matplotlib. I'll try to find out tomorrow what the problem is.
Did you find anything? Can I help you in some way? I profiled the execution and indeed it's matplotlib's drawing functions that are slow, but I don't know enough to understand what happens
This is the full log of cProfile gr_backend.log
After tests with cProfile
I can unfortunately only confirm that most of the time is spent in the Python savefig
part. Optimizing the GR wrapper would not help until the root cause is found.
We'll try something else. Anyway, thank you for your time!
I am developing a desktop application that utilizes Matplotlib for plotting purposes. To improve performance, I have chosen the GR framework as the backend for Matplotlib, since it's 100 times faster. (nice!)
However, I am currently facing an issue where exporting plots to SVG:
The GR backend does not support svg output
errorWhile researching a solution, I found this old closed issue: gr as backend for matplotlib: Can I save images as png? Interestingly enough, a response suggested that exporting to SVG format would be an easier alternative, indicating the potential existence of SVG export capability with GR as a backend. Did I misunderstand something?
OTOH, if this feature is missing, would it be hard to add it? What would it take to implement it? I'm willing to help, but I know nothing about matplotlib's backends...
Thank you for your attention to this matter
Update: I patched the backend to save SVGs, but it does it as slow as mpl, so I'm clearly doing something wrong...