matplotlib / matplotlib/ipympl

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

未关闭
#565 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

主要语言
Jupyter Notebook
星标
1.7k
派生
234
PR 合并指标
30 天内没有已合并 PR

描述

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 = """
            <div style="display: inline-block;">
                <div class="jupyter-widgets widget-label" style="text-align: center;">
                    {}
                </div>
                <img src='{}' width={} dpi={} dpi-ratio={}/>
            </div>
        """.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 = """
            <div style="display: inline-block;">
                <div class="jupyter-widgets widget-label" style="text-align: center;">
                    {}
                </div>
                <img src='{}' width={} dpi={} dpi-ratio={}/>
            </div>
        """.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.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 backend_nbagg.py 中 repr_mimebundle 附近开始,并在 JupyterLab 或 VSCode 的 Retina 显示屏上使用 %matplotlib widget 重现该问题。跟踪 device_pixel_ratio 如何传递到 HTML 和 widget 表示中,并将其与 fig.canvas.device_pixel_ratio 进行比较。当导出或打印的图像保留预期的高分辨率像素,而不是从低分辨率 PNG 放大而来时,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
jupyter-notebook, python
领域
backend, frontend
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
42/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。