matplotlib / matplotlib/ipympl

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

Offen
#565 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Vorherrschende Sprache
Jupyter Notebook
Sterne
1.7k
Forks
234
PR-Merge-Kennzahlen
Keine gemergten PRs in 30 T.

Beschreibung

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.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne in backend_nbagg.py bei repr_mimebundle und reproduziere das Problem mit %matplotlib widget auf einem Retina-Display in JupyterLab oder VSCode. Verfolge, wie device_pixel_ratio die HTML- und Widget-Repräsentation erreicht, und vergleiche es mit fig.canvas.device_pixel_ratio. Als erledigt gilt die Aufgabe, wenn exportierte oder gedruckte Abbildungen die erwartete Anzahl hochauflösender Pixel beibehalten, statt aus einem PNG mit niedriger Auflösung hochskaliert zu werden.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
jupyter-notebook, python
Bereich
backend, frontend
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
42/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.