Skip to content

Commit

Permalink
Improve Figure.show for displaying previews in Jupyter notebooks and …
Browse files Browse the repository at this point in the history
…external viewers (GenericMappingTools#529)

Figure.show() now is able to detect the current running environment,
and display previews in Jupyter notebooks, or open previews using
external viewers.

This PR also provides `pygmt.set_display()` to change the display method
globally.

Setting environmental variable `PYGMT_USE_EXTERNAL_DISPLAY ` to `false`
can disable external viewers, mostly for running tests and building the
documentations.

Figure.show() no longer returns the Image object.

Co-authored-by: Wei Ji <[email protected]>
Co-authored-by: Meghan Jones <[email protected]>
  • Loading branch information
3 people authored and Josh Sixsmith committed Dec 21, 2022
1 parent 7c4bb30 commit cb2d4b5
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 45 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test:
@echo ""
@cd $(TESTDIR); python -c "import $(PROJECT); $(PROJECT).show_versions()"
@echo ""
cd $(TESTDIR); pytest $(PYTEST_COV_ARGS) $(PROJECT)
cd $(TESTDIR); PYGMT_USE_EXTERNAL_DISPLAY="false" pytest $(PYTEST_COV_ARGS) $(PROJECT)
cp $(TESTDIR)/coverage.xml .
cp -r $(TESTDIR)/htmlcov .
rm -r $(TESTDIR)
Expand Down
3 changes: 2 additions & 1 deletion doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ html: api
@echo
@echo "Building HTML files."
@echo
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
# Set PYGMT_USE_EXTERNAL_DISPLAY to "false" to disable external display
PYGMT_USE_EXTERNAL_DISPLAY="false" $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

Expand Down
7 changes: 7 additions & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ Saving and displaying the figure:
Figure.show
Figure.psconvert

Configuring the display settings:

.. autosummary::
:toctree: generated

set_display


Data Processing
---------------
Expand Down
2 changes: 1 addition & 1 deletion pygmt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# Import modules to make the high-level GMT Python API
from pygmt import datasets
from pygmt.accessors import GMTDataArrayAccessor
from pygmt.figure import Figure
from pygmt.figure import Figure, set_display
from pygmt.session_management import begin as _begin
from pygmt.session_management import end as _end
from pygmt.src import (
Expand Down
130 changes: 91 additions & 39 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from tempfile import TemporaryDirectory

try:
from IPython.display import Image
except ImportError:
Image = None
import IPython
except KeyError:
IPython = None # pylint: disable=invalid-name


from pygmt.clib import Session
from pygmt.exceptions import GMTError, GMTInvalidInput
Expand All @@ -25,6 +26,23 @@
# This is needed for the sphinx-gallery scraper in pygmt/sphinx_gallery.py
SHOWED_FIGURES = []

# Configurations for figure display
SHOW_CONFIG = {
"method": "external", # Open in an external viewer by default
}

# Show figures in Jupyter notebooks if available
if IPython:
get_ipython = IPython.get_ipython() # pylint: disable=invalid-name
if get_ipython and "IPKernelApp" in get_ipython.config: # Jupyter Notebook enabled
SHOW_CONFIG["method"] = "notebook"

# Set environment variable PYGMT_USE_EXTERNAL_DISPLAY to 'false' to disable
# external display. Use it when running the tests and building the docs to
# avoid popping up windows.
if os.environ.get("PYGMT_USE_EXTERNAL_DISPLAY", "true").lower() == "false":
SHOW_CONFIG["method"] = "none"


class Figure:
"""
Expand Down Expand Up @@ -235,62 +253,72 @@ def savefig(
if show:
launch_external_viewer(fname)

def show(self, dpi=300, width=500, method="static"):
def show(self, dpi=300, width=500, method=None):
"""
Display a preview of the figure.
Inserts the preview in the Jupyter notebook output. You will need to
have IPython installed for this to work. You should have it if you are
using the notebook.
Inserts the preview in the Jupyter notebook output if available,
otherwise opens it in the default viewer for your operating system
(falls back to the default web browser).
:func:`pygmt.set_display` can select the default display method
(**notebook**, **external**, or **none**).
The ``method`` parameter can also override the default display method
for the current figure. Parameters ``dpi`` and ``width`` can be used
to control the resolution and dimension of the figure in the notebook.
Note: The external viewer can be disabled by setting the
PYGMT_USE_EXTERNAL_DISPLAY environment variable to **false**.
This is useful when running unit tests and building the documentation
in consoles without a Graphical User Interface.
If ``method='external'``, makes PDF preview instead and opens it in the
default viewer for your operating system (falls back to the default web
browser). Note that the external viewer does not block the current
process, so this won't work in a script.
Note that the external viewer does not block the current process.
Parameters
----------
dpi : int
The image resolution (dots per inch).
The image resolution (dots per inch) in Jupyter notebooks.
width : int
Width of the figure shown in the notebook in pixels. Ignored if
``method='external'``.
The image width (in pixels) in Jupyter notebooks.
method : str
How the figure will be displayed. Options are (1) ``'static'``: PNG
preview (default); (2) ``'external'``: PDF preview in an external
program.
How the current figure will be displayed. Options are
Returns
-------
img : IPython.display.Image
Only if ``method != 'external'``.
- **external**: PDF preview in an external program [default]
- **notebook**: PNG preview [default in Jupyter notebooks]
- **none**: Disable image preview
"""
# Module level variable to know which figures had their show method
# called. Needed for the sphinx-gallery scraper.
SHOWED_FIGURES.append(self)

if method not in ["static", "external"]:
raise GMTInvalidInput("Invalid show method '{}'.".format(method))
if method == "external":
pdf = self._preview(fmt="pdf", dpi=dpi, anti_alias=False, as_bytes=False)
launch_external_viewer(pdf)
img = None
elif method == "static":
png = self._preview(
fmt="png", dpi=dpi, anti_alias=True, as_bytes=True, transparent=True
# Set the display method
if method is None:
method = SHOW_CONFIG["method"]

if method not in ["external", "notebook", "none"]:
raise GMTInvalidInput(
(
f"Invalid display method '{method}', "
"should be either 'notebook', 'external', or 'none'."
)
)
if Image is None:

if method in ["notebook", "none"]:
if IPython is None:
raise GMTError(
" ".join(
[
"Cannot find IPython.",
"Make sure you have it installed",
"or use 'method=\"external\"' to open in an external viewer.",
]
(
"Notebook display is selected, but IPython is not available. "
"Make sure you have IPython installed, "
"or run the script in a Jupyter notebook."
)
)
img = Image(data=png, width=width)
return img
png = self._preview(fmt="png", dpi=dpi, anti_alias=True, as_bytes=True)
IPython.display.display(IPython.display.Image(data=png, width=width))

if method == "external":
pdf = self._preview(fmt="pdf", dpi=dpi, anti_alias=False, as_bytes=False)
launch_external_viewer(pdf)

def shift_origin(self, xshift=None, yshift=None):
"""
Expand Down Expand Up @@ -396,3 +424,27 @@ def _repr_html_(self):
subplot,
text,
)


def set_display(method=None):
"""
Set the display method.
Parameters
----------
method : str or None
The method to display an image. Choose from:
- **external**: PDF preview in an external program [default]
- **notebook**: PNG preview [default in Jupyter notebooks]
- **none**: Disable image preview
"""
if method in ["notebook", "external", "none"]:
SHOW_CONFIG["method"] = method
elif method is not None:
raise GMTInvalidInput(
(
f"Invalid display mode '{method}', "
"should be either 'notebook', 'external' or 'none'."
)
)
14 changes: 11 additions & 3 deletions pygmt/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import numpy as np
import numpy.testing as npt
import pytest
from pygmt import Figure
from pygmt import Figure, set_display
from pygmt.exceptions import GMTInvalidInput


Expand Down Expand Up @@ -142,8 +142,7 @@ def test_figure_show():
"""
fig = Figure()
fig.basemap(region="10/70/-300/800", projection="X3i/5i", frame="af")
img = fig.show(width=800)
assert img.width == 800
fig.show()


@pytest.mark.mpl_image_compare
Expand Down Expand Up @@ -175,3 +174,12 @@ def test_figure_show_invalid_method():
fig.basemap(region="10/70/-300/800", projection="X3i/5i", frame="af")
with pytest.raises(GMTInvalidInput):
fig.show(method="test")


def test_figure_set_display_invalid():
"""
Test to check if an error is raised when an invalid method is passed to
set_display.
"""
with pytest.raises(GMTInvalidInput):
set_display(method="invalid")

0 comments on commit cb2d4b5

Please sign in to comment.