Skip to content

Commit fc3f918

Browse files
committed
FIX: Faster img compression too
1 parent 6e6b795 commit fc3f918

3 files changed

Lines changed: 39 additions & 18 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Sped up various evoked plotting functions by taking advantage of blitting, by `Eric Larson`_
1+
Sped up various evoked plotting functions by taking advantage of blitting, and :class:`mne.Report` image embedding by compressing rendered pixels directly, by `Eric Larson`_

mne/report/report.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,26 @@ def _constrain_fig_resolution(fig, *, max_width, max_res):
408408
fig.set_dpi(dpi)
409409

410410

411+
def _compress_img(img, image_format, dpi):
412+
"""Drop the alpha channel and compress, for space and to avoid rendering issues."""
413+
from PIL import Image
414+
415+
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
416+
pil_kwargs = dict()
417+
if image_format == "webp":
418+
# Here quality means speed/size tradeoff (either way the result is lossless);
419+
# 20 rather than 50 encodes ~1.5x faster for a few percent more bytes
420+
pil_kwargs.update(lossless=True, quality=20)
421+
else:
422+
assert image_format == "png", image_format
423+
pil_kwargs.update(optimize=True, compress_level=9)
424+
background = Image.new("RGBA", img.size, (255, 255, 255))
425+
img = Image.alpha_composite(background, img).convert("RGB")
426+
output = BytesIO()
427+
img.save(output, format=image_format, dpi=(dpi, dpi), **pil_kwargs)
428+
return output
429+
430+
411431
def _fig_to_img(
412432
fig,
413433
*,
@@ -425,10 +445,22 @@ def _fig_to_img(
425445
if isinstance(fig, np.ndarray):
426446
# In this case, we are creating the fig, so we might as well
427447
# auto-close in all cases
428-
fig = _ndarray_to_fig(fig)
448+
img, fig = fig, _ndarray_to_fig(fig)
449+
dpi = fig.get_dpi()
429450
if own_figure:
430451
_constrain_fig_resolution(fig, max_width=max_width, max_res=max_res)
431452
own_figure = True # close the figure we just created
453+
if fig.get_dpi() == dpi and image_format in ("png", "webp"):
454+
# Nothing rescaled the pixels, so compress them as they are rather than
455+
# rendering them back through the figure only to read them out again
456+
from PIL import Image
457+
458+
plt.close(fig)
459+
if img.dtype.kind == "f": # float in [0, 1], as _fig_to_img returns
460+
img = np.clip(img, 0, 1) * 255
461+
img = Image.fromarray(img.astype(np.uint8)).convert("RGBA")
462+
output = _compress_img(img, image_format, dpi)
463+
return base64.b64encode(output.getvalue()).decode("ascii")
432464
elif isinstance(fig, Figure):
433465
if own_figure:
434466
_constrain_fig_resolution(fig, max_width=max_width, max_res=max_res)
@@ -484,20 +516,10 @@ def _fig_to_img(
484516
if image_format not in ("svg", "ndarray"):
485517
from PIL import Image
486518

487-
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
488-
pil_kwargs = dict()
489-
if image_format == "webp":
490-
# Here quality means speed/size tradeoff (either way the result is lossless)
491-
pil_kwargs.update(lossless=True, quality=50)
492-
elif image_format == "png":
493-
pil_kwargs.update(optimize=True, compress_level=9)
494519
output.seek(0)
495520
orig = Image.open(output)
496521
if orig.mode == "RGBA":
497-
background = Image.new("RGBA", orig.size, (255, 255, 255))
498-
new = Image.alpha_composite(background, orig).convert("RGB")
499-
output = BytesIO()
500-
new.save(output, format=image_format, dpi=(dpi, dpi), **pil_kwargs)
522+
output = _compress_img(orig, image_format, dpi)
501523

502524
if image_format == "ndarray":
503525
# float in [0, 1], like the PNG this used to go through

tutorials/intro/70_report.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,10 @@
156156
# Adding `~mne.Covariance`
157157
# ^^^^^^^^^^^^^^^^^^^^^^^^
158158
#
159-
# (Noise) covariance objects can be added via
160-
# :meth:`mne.Report.add_covariance`. The method accepts `~mne.Covariance`
161-
# objects and the path to a file on disk. It also expects us to pass an
162-
# `~mne.Info` object or the path to a file to read the measurement info from,
163-
# as well as a title.
159+
# (Noise) covariance objects can be added via :meth:`mne.Report.add_covariance`. The
160+
# method accepts `~mne.Covariance` objects and the path to a file on disk. It also
161+
# expects us to pass an `~mne.Info` object or the path to a file to read the measurement
162+
# info from, as well as a title.
164163

165164
cov_path = sample_dir / "sample_audvis-cov.fif"
166165

0 commit comments

Comments
 (0)