matplotlib / matplotlib/ipympl

Exporting/Printing always saves to low-res PNG; device_pixel_ratio doesn't seem to be set correctly

Open
#565 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Jupyter Notebook
Stars
1.7k
Forks
234
PR merge metrics
No merged PRs in 30d

Description

## Describe the issue
If I'm using `%matplotlib widget`, and then I try to print, export, etc, it always prints to a low-res PNG. Inspection of the notebook reveals that ipympl stores a low-res PNG alongside the reference to the interactive widget, and then (naively) scales it based on the DPI. I can set the DPI via the figure

The culprit seems to be around line 327 of backend_nbagg.py:

```
def _repr_mimebundle_(self, **kwargs):
# now happens before the actual display call.
if hasattr(self, '_handle_displayed'):
self._handle_displayed(**kwargs)
plaintext = repr(self)
if len(plaintext) > 110:
plaintext = plaintext[:110] + '…'

buf = io.BytesIO()
self.figure.savefig(buf, format='png', dpi='figure')

base64_image = b64encode(buf.getvalue()).decode('utf-8')
self._data_url = f'data:image/png;base64,{base64_image}'
# Figure size in pixels
pwidth = self.figure.get_figwidth() * self.figure.get_dpi()
pheight = self.figure.get_figheight() * self.figure.get_dpi()
# Scale size to match widget on HiDPI monitors.
if hasattr(self, 'device_pixel_ratio'): # Matplotlib 3.5+
width = pwidth / self.device_pixel_ratio
height = pheight / self.device_pixel_ratio
else:
width = pwidth / self._dpi_ratio
height = pheight / self._dpi_ratio
html = """



{}



""".format(
self._figure_label, self._data_url, width, self.figure.get_dpi(), self.device_pixel_ratio
)

# Update the widget model properly for HTML embedding
self._size = (width, height)

data = {
'text/plain': plaintext,
'image/png': base64_image,
'text/html': html,
'application/vnd.jupyter.widget-view+json': {
'version_major': 2,
'version_minor': 0,
'model_id': self._model_id,
},
}

return data
```

Note that I've added a bit of debugging here:

```
html = """



{}



""".format(
self._figure_label, self._data_url, width, self.figure.get_dpi(), self.device_pixel_ratio
```

so that it outputs the dpi and pixel ratio as well. For whatever reason, the dpi-ratio is set to **1**, even though I'm on a Retina screen where it should be 2:

```
Python 3.11.9 | packaged by conda-forge | (main, Apr 19 2024, 18:34:54) [Clang 16.0.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> fig.canvas.device_pixel_ratio
2.0
```

The dpi is set to 100. These two things partly cancel out, so that I end up getting a low-res image, but we aren't dividing the pwidth by 2 (as it thinks the pixel ratio is 1). The result is a pixelated, upscaled, low-res image.

In short, I'm not sure where it's getting the pixel ratio from, but it doesn't seem to be getting set correctly on my system. I've tried this both in JupyterLab and VSCode.

## Versions

```
$ python -c "import sys; print('\n',sys.version); import ipympl; print('ipympl version:', ipympl.__version__)" && jupyter --version && jupyter nbextension list && jupyter labextension list

3.11.9 | packaged by conda-forge | (main, Apr 19 2024, 18:34:54) [Clang 16.0.6 ]
ipympl version: 0.9.4
Selected Jupyter core packages...
IPython : 8.17.2
ipykernel : 6.29.4
ipywidgets : 8.1.3
jupyter_client : 8.6.2
jupyter_core : 5.7.2
jupyter_server : 2.14.1
jupyterlab : 4.2.2
nbclient : 0.10.0
nbconvert : 7.16.4
nbformat : 5.10.4
notebook : 7.2.1
qtconsole : 5.5.2
traitlets : 5.14.3
usage: jupyter [-h] [--version] [--config-dir] [--data-dir] [--runtime-dir] [--paths] [--json] [--debug] [subcommand]

Jupyter: Interactive Computing

positional arguments:
subcommand the subcommand to launch

options:
-h, --help show this help message and exit
--version show the versions of core jupyter packages and exit
--config-dir show Jupyter config dir
--data-dir show Jupyter data dir
--runtime-dir show Jupyter runtime dir
--paths show all Jupyter paths. Add --json for machine-readable format.
--json output paths as machine-readable json
--debug output debug information about paths

Available subcommands: console dejavu events execute kernel kernelspec lab labextension labhub migrate nbclassic nbconvert notebook qtconsole run server
troubleshoot trust

Jupyter command `jupyter-nbextension` not found.
```

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with backend_nbagg.py around _repr_mimebundle_ and reproduce the issue using %matplotlib widget on a Retina display in JupyterLab or VSCode. Trace how device_pixel_ratio reaches the HTML and widget representation, comparing it with fig.canvas.device_pixel_ratio. Done means exported or printed figures retain the expected high-resolution pixels rather than being upscaled from a low-resolution PNG.

Written by the indexing model from the issue text.

Assessment

Tech stack
jupyter-notebook, python
Domain
backend, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.