Skip to content

Commit 7bdc47d

Browse files
committed
Merge branch 'hotfix/1.7.2'
2 parents 8cc2d3f + a1c776d commit 7bdc47d

File tree

6 files changed

+163
-107
lines changed

6 files changed

+163
-107
lines changed

CHANGELOG.rst

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
Changelog for Metview's Python interface
33
========================================
44

5+
1.7.2
6+
------------------
7+
- new argument to setoutput(plot_widget=) - default True, set False to allow images to be saved into the notebook
8+
- multi-page plots in Jupyter notebooks now contain the animation controls by default
9+
10+
511
1.7.1
612
------------------
713
- added automatic play and speed controls to animated plots in Jupyter notebooks

metview/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# requires a Python 3 interpreter
1111
import sys
1212

13-
if sys.version_info[0] < 3:
13+
if sys.version_info[0] < 3: # pragma: no cover
1414
raise EnvironmentError(
1515
"Metview's Python interface requires Python 3. You are using Python "
1616
+ repr(sys.version_info)

metview/bindings.py

+109-99
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import numpy as np
2222

2323

24-
__version__ = "1.7.1"
24+
__version__ = "1.7.2"
2525

2626

2727
def string_from_ffi(s):
@@ -45,7 +45,7 @@ def __init__(self):
4545
self.debug = os.environ.get("METVIEW_PYTHON_DEBUG", "0") == "1"
4646

4747
# check whether we're in a running Metview session
48-
if "METVIEW_TITLE_PROD" in os.environ:
48+
if "METVIEW_TITLE_PROD" in os.environ: # pragma: no cover
4949
self.persistent_session = True
5050
self.info_section = {"METVIEW_LIB": os.environ["METVIEW_LIB"]}
5151
return
@@ -54,7 +54,7 @@ def __init__(self):
5454
import time
5555
import subprocess
5656

57-
if self.debug:
57+
if self.debug: # pragma: no cover
5858
print("MetviewInvoker: Invoking Metview")
5959
self.persistent_session = False
6060
self.metview_replied = False
@@ -77,14 +77,14 @@ def __init__(self):
7777
env_file.name,
7878
str(pid),
7979
]
80-
if self.debug:
80+
if self.debug: # pragma: no cover
8181
metview_flags.insert(2, "-slog")
8282
print("Starting Metview using these command args:")
8383
print(metview_flags)
8484

8585
try:
8686
subprocess.Popen(metview_flags)
87-
except Exception as exp:
87+
except Exception as exp: # pragma: no cover
8888
print(
8989
"Could not run the Metview executable ('" + metview_startup_cmd + "'); "
9090
"check that the binaries for Metview (version 5 at least) are installed "
@@ -99,7 +99,7 @@ def __init__(self):
9999
):
100100
time.sleep(0.001)
101101

102-
if not (self.metview_replied):
102+
if not (self.metview_replied): # pragma: no cover
103103
raise Exception(
104104
'Command "metview" did not respond within '
105105
+ str(self.metview_startup_timeout)
@@ -121,7 +121,7 @@ def __init__(self):
121121
def destroy(self):
122122
"""Kills the Metview session. Raises an exception if it could not do it."""
123123

124-
if self.persistent_session:
124+
if self.persistent_session: # pragma: no cover
125125
return
126126

127127
if self.metview_replied:
@@ -188,7 +188,7 @@ def restore_signal_handlers(self):
188188
# MacOS systems
189189
lib = ffi.dlopen(os.path.join(mv_lib, "libMvMacro"))
190190

191-
except Exception as exp:
191+
except Exception as exp: # pragma: no cover
192192
print(
193193
"Error loading Metview/libMvMacro. LD_LIBRARY_PATH="
194194
+ os.environ.get("LD_LIBRARY_PATH", "")
@@ -597,7 +597,7 @@ def to_dataset(self, **kwarg):
597597
# soft dependency on cfgrib
598598
try:
599599
import xarray as xr
600-
except ImportError:
600+
except ImportError: # pragma: no cover
601601
print("Package xarray not found. Try running 'pip install xarray'.")
602602
raise
603603
dataset = xr.open_dataset(self.url(), engine="cfgrib", backend_kwargs=kwarg)
@@ -639,7 +639,7 @@ def __init__(self, val_pointer):
639639
def to_dataframe(self):
640640
try:
641641
import pandas as pd
642-
except ImportError:
642+
except ImportError: # pragma: no cover
643643
print("Package pandas not found. Try running 'pip install pandas'.")
644644
raise
645645

@@ -665,7 +665,7 @@ def to_dataset(self):
665665
# soft dependency on xarray
666666
try:
667667
import xarray as xr
668-
except ImportError:
668+
except ImportError: # pragma: no cover
669669
print("Package xarray not found. Try running 'pip install xarray'.")
670670
raise
671671
dataset = xr.open_dataset(self.url())
@@ -679,7 +679,7 @@ def __init__(self, val_pointer):
679679
def to_dataframe(self):
680680
try:
681681
import pandas as pd
682-
except ImportError:
682+
except ImportError: # pragma: no cover
683683
print("Package pandas not found. Try running 'pip install pandas'.")
684684
raise
685685

@@ -700,7 +700,7 @@ def __init__(self, val_pointer):
700700
def to_dataframe(self):
701701
try:
702702
import pandas as pd
703-
except ImportError:
703+
except ImportError: # pragma: no cover
704704
print("Package pandas not found. Try running 'pip install pandas'.")
705705
raise
706706

@@ -867,7 +867,7 @@ def vector_from_metview(val):
867867
elif s == 8:
868868
nptype = np.float64
869869
b = lib.p_vector_double_array(vec)
870-
else:
870+
else: # pragma: no cover
871871
raise Exception("Metview vector data type cannot be handled: ", s)
872872

873873
bsize = n * s
@@ -1080,28 +1080,15 @@ def merge(*args):
10801080
class Plot:
10811081
def __init__(self):
10821082
self.plot_to_jupyter = False
1083+
self.plot_widget = True
10831084
self.jupyter_args = {}
10841085

10851086
def __call__(self, *args, **kwargs):
1086-
# if animate=True is supplied, then create a Jupyter animation
1087-
if kwargs.get("animate", False):
1088-
return animate(args, kwargs)
1089-
1090-
# otherwise create a single static plot
1091-
if self.plot_to_jupyter:
1092-
f, tmp = tempfile.mkstemp(".png")
1093-
os.close(f)
1094-
1095-
base, ext = os.path.splitext(tmp)
1096-
1097-
output_args = {"output_name": base, "output_name_first_page_number": "off"}
1098-
output_args.update(self.jupyter_args)
1099-
met_setoutput(png_output(output_args))
1100-
met_plot(*args)
1101-
1102-
image = Image(tmp)
1103-
os.unlink(tmp)
1104-
return image
1087+
if self.plot_to_jupyter: # pragma: no cover
1088+
if self.plot_widget:
1089+
return plot_to_notebook(args, **kwargs)
1090+
else:
1091+
return plot_to_notebook_return_image(args, **kwargs)
11051092
else:
11061093
map_outputs = {
11071094
"png": png_output,
@@ -1122,14 +1109,9 @@ def __call__(self, *args, **kwargs):
11221109

11231110
# animate - only usable within Jupyter notebooks
11241111
# generates a widget allowing the user to select between plot frames
1125-
def animate(*args, **kwargs):
1126-
1127-
if not plot.plot_to_jupyter:
1128-
raise EnvironmentError(
1129-
"animate() can only be used after calling set_output('jupyter')"
1130-
)
1112+
def plot_to_notebook(*args, **kwargs): # pragma: no cover
11311113

1132-
import ipywidgets as widgets
1114+
animation_mode = kwargs.get("animate", "auto") # True, False or "auto"
11331115

11341116
# create all the widgets first so that the 'waiting' label is at the bottom
11351117
image_widget = widgets.Image(
@@ -1138,47 +1120,9 @@ def animate(*args, **kwargs):
11381120
# height=400,
11391121
)
11401122

1141-
frame_widget = widgets.IntSlider(
1142-
value=1,
1143-
min=1,
1144-
max=1,
1145-
step=1,
1146-
description="Frame:",
1147-
disabled=False,
1148-
continuous_update=True,
1149-
readout=True,
1150-
)
1151-
1152-
play_widget = widgets.Play(
1153-
value=1,
1154-
min=1,
1155-
max=1,
1156-
step=1,
1157-
interval=500,
1158-
description="Play animation",
1159-
disabled=False,
1160-
)
1161-
1162-
speed_widget = widgets.IntSlider(
1163-
value=3,
1164-
min=1,
1165-
max=20,
1166-
step=1,
1167-
description="Speed",
1168-
disabled=False,
1169-
continuous_update=True,
1170-
readout=True,
1171-
)
1172-
1173-
widgets.jslink((play_widget, "value"), (frame_widget, "value"))
1174-
play_and_speed_widget = widgets.HBox([play_widget, speed_widget])
1175-
controls = widgets.VBox([frame_widget, play_and_speed_widget])
1176-
1177-
controls.layout.visibility = "hidden"
11781123
image_widget.layout.visibility = "hidden"
11791124
waitl_widget = widgets.Label(value="Generating plots....")
1180-
frame_widget.layout.width = "800px"
1181-
display(image_widget, controls, waitl_widget)
1125+
display(image_widget, waitl_widget)
11821126

11831127
# plot all frames to a temporary directory owned by Metview to enure cleanup
11841128
tempdirpath = tempfile.mkdtemp(dir=os.environ.get("METVIEW_TMPDIR", None))
@@ -1196,45 +1140,98 @@ def animate(*args, **kwargs):
11961140
return
11971141

11981142
files = [os.path.join(tempdirpath, f) for f in sorted(filenames)]
1199-
frame_widget.max = len(files)
1200-
frame_widget.description = "Frame (" + str(len(files)) + ") :"
1201-
play_widget.max = len(files)
1143+
1144+
if (animation_mode == True) or (animation_mode == "auto" and len(filenames) > 1):
1145+
frame_widget = widgets.IntSlider(
1146+
value=1,
1147+
min=1,
1148+
max=1,
1149+
step=1,
1150+
description="Frame:",
1151+
disabled=False,
1152+
continuous_update=True,
1153+
readout=True,
1154+
)
1155+
1156+
play_widget = widgets.Play(
1157+
value=1,
1158+
min=1,
1159+
max=1,
1160+
step=1,
1161+
interval=500,
1162+
description="Play animation",
1163+
disabled=False,
1164+
)
1165+
1166+
speed_widget = widgets.IntSlider(
1167+
value=3,
1168+
min=1,
1169+
max=20,
1170+
step=1,
1171+
description="Speed",
1172+
disabled=False,
1173+
continuous_update=True,
1174+
readout=True,
1175+
)
1176+
1177+
widgets.jslink((play_widget, "value"), (frame_widget, "value"))
1178+
play_and_speed_widget = widgets.HBox([play_widget, speed_widget])
1179+
controls = widgets.VBox([frame_widget, play_and_speed_widget])
1180+
controls.layout.visibility = "hidden"
1181+
frame_widget.layout.width = "800px"
1182+
display(controls)
1183+
1184+
frame_widget.max = len(files)
1185+
frame_widget.description = "Frame (" + str(len(files)) + ") :"
1186+
play_widget.max = len(files)
1187+
1188+
def on_frame_change(change):
1189+
plot_frame(change["new"])
1190+
1191+
def on_speed_change(change):
1192+
play_widget.interval = 1500 / change["new"]
1193+
1194+
frame_widget.observe(on_frame_change, names="value")
1195+
speed_widget.observe(on_speed_change, names="value")
1196+
controls.layout.visibility = "visible"
12021197

12031198
def plot_frame(frame_index):
12041199
im_file = open(files[frame_index - 1], "rb")
12051200
imf = im_file.read()
12061201
im_file.close()
12071202
image_widget.value = imf
12081203

1209-
def on_frame_change(change):
1210-
plot_frame(change["new"])
1211-
1204+
# everything is ready now, so plot the first frame, hide the
1205+
# 'waiting' label and reveal the plot and the frame slider
12121206
plot_frame(1)
1213-
frame_widget.observe(on_frame_change, names="value")
1207+
waitl_widget.layout.visibility = "hidden"
1208+
image_widget.layout.visibility = "visible"
12141209

1215-
def on_speed_change(change):
1216-
play_widget.interval = 1500 / change["new"]
12171210

1218-
speed_widget.observe(on_speed_change, names="value")
1211+
def plot_to_notebook_return_image(*args, **kwargs): # pragma: no cover
12191212

1220-
# everything is ready now, so hide the 'waiting' label
1221-
# and reveal the plot and the frame slider
1222-
waitl_widget.layout.visibility = "hidden"
1223-
controls.layout.visibility = "visible"
1224-
image_widget.layout.visibility = "visible"
1213+
from IPython.display import Image
1214+
1215+
f, tmp = tempfile.mkstemp(".png")
1216+
os.close(f)
1217+
base, ext = os.path.splitext(tmp)
1218+
plot.jupyter_args.update(output_name=base, output_name_first_page_number="off")
1219+
met_setoutput(png_output(plot.jupyter_args))
1220+
met_plot(*args)
1221+
image = Image(tmp)
1222+
os.unlink(tmp)
1223+
return image
12251224

12261225

12271226
# On a test system, importing IPython took approx 0.5 seconds, so to avoid that hit
12281227
# under most circumstances, we only import it when the user asks for Jupyter
12291228
# functionality. Since this occurs within a function, we need a little trickery to
12301229
# get the IPython functions into the global namespace so that the plot object can use them
12311230
def setoutput(*args, **kwargs):
1232-
if "jupyter" in args:
1231+
if "jupyter" in args: # pragma: no cover
12331232
try:
1234-
global Image
1235-
global get_ipython
1236-
IPython = __import__("IPython", globals(), locals())
1237-
Image = IPython.display.Image
1233+
import IPython
1234+
12381235
get_ipython = IPython.get_ipython
12391236
except ImportError as imperr:
12401237
print("Could not import IPython module - plotting to Jupyter will not work")
@@ -1243,12 +1240,25 @@ def setoutput(*args, **kwargs):
12431240
# test whether we're in the Jupyter environment
12441241
if get_ipython() is not None:
12451242
plot.plot_to_jupyter = True
1243+
plot.plot_widget = kwargs.get("plot_widget", True)
1244+
if "plot_widget" in kwargs:
1245+
del kwargs["plot_widget"]
12461246
plot.jupyter_args = kwargs
12471247
else:
12481248
print(
12491249
"ERROR: setoutput('jupyter') was set, but we are not in a Jupyter environment"
12501250
)
12511251
raise (Exception("Could not set output to jupyter"))
1252+
1253+
try:
1254+
global widgets
1255+
widgets = __import__("ipywidgets", globals(), locals())
1256+
except ImportError as imperr:
1257+
print(
1258+
"Could not import ipywidgets module - plotting to Jupyter will not work"
1259+
)
1260+
raise imperr
1261+
12521262
else:
12531263
plot.plot_to_jupyter = False
12541264
met_setoutput(*args)

tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)