Skip to content

Commit

Permalink
Make plot resolution configurable
Browse files Browse the repository at this point in the history
Set plotting resolution from workflow config via rose edit. The plot
resolution is then read via a function. This keeps importing the module
from having side effects.

Setting and getting plot resolution is tested.
  • Loading branch information
jfrost-mo committed Aug 20, 2024
1 parent dfed8e0 commit 7dfd564
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 13 deletions.
2 changes: 2 additions & 0 deletions cset-workflow/app/run_cset_recipe/bin/run-cset-recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def parallel():
f"--input-dir={data_directory()}",
f"--output-dir={output_directory()}",
f"--style-file={os.getenv('COLORBAR_FILE', '')}",
f"--plot-resolution={os.getenv('PLOT_RESOLUTION', '')}",
"--parallel-only",
),
check=True,
Expand Down Expand Up @@ -188,6 +189,7 @@ def collate():
f"--recipe={recipe_file()}",
f"--output-dir={output_directory()}",
f"--style-file={os.getenv('COLORBAR_FILE', '')}",
f"--plot-resolution={os.getenv('PLOT_RESOLUTION', '')}",
"--collate-only",
),
check=True,
Expand Down
1 change: 1 addition & 0 deletions cset-workflow/flow.cylc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ URL = https://metoffice.github.io/CSET
LOGLEVEL = {{LOGLEVEL}}
WEB_DIR = {{WEB_DIR}}
COLORBAR_FILE = {{COLORBAR_FILE}}
PLOT_RESOLUTION = {{PLOT_RESOLUTION}}

[[PARALLEL]]
script = rose task-run -v --app-key=run_cset_recipe
Expand Down
10 changes: 10 additions & 0 deletions cset-workflow/meta/rose-meta.conf
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@ help=
type=quoted
compulsory=true

[template variables=PLOT_RESOLUTION]
ns=General
description=Resolution of output plot in dpi.
help=This is passed through to the plotting operators and sets the resolution
of the output plots to the given number of pixels per inch. If unset
defaults to 100 dpi. The plots are all 8 by 8 inches, so this corresponds
to 800 by 800 pixels.
type=integer
compulsory=true

[template variables=WEB_DIR]
ns=General
description=Path to directory that is served by the webserver.
Expand Down
10 changes: 9 additions & 1 deletion src/CSET/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ def main():
parser_bake.add_argument(
"-s", "--style-file", type=Path, help="colour bar definition to use"
)
parser_bake.add_argument(
"--plot-resolution", type=int, help="plotting resolution in dpi"
)
parser_bake.set_defaults(func=_bake_command)

parser_graph = subparsers.add_parser("graph", help="visualise a recipe file")
Expand Down Expand Up @@ -207,10 +210,15 @@ def _bake_command(args, unparsed_args):
args.output_dir,
recipe_variables,
args.style_file,
args.plot_resolution,
)
if not args.parallel_only:
execute_recipe_collate(
args.recipe, args.output_dir, recipe_variables, args.style_file
args.recipe,
args.output_dir,
recipe_variables,
args.style_file,
args.plot_resolution,
)


Expand Down
29 changes: 25 additions & 4 deletions src/CSET/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,14 @@ def _step_parser(step: dict, step_input: any) -> str:
return operator(**kwargs)


def _run_steps(recipe, steps, step_input, output_directory: Path, style_file: Path):
def _run_steps(
recipe,
steps,
step_input,
output_directory: Path,
style_file: Path = None,
plot_resolution: int = None,
) -> None:
"""Execute the steps in a recipe."""
original_working_directory = Path.cwd()
os.chdir(output_directory)
Expand All @@ -159,6 +166,8 @@ def _run_steps(recipe, steps, step_input, output_directory: Path, style_file: Pa
# Create metadata file used by some steps.
if style_file:
recipe["style_file_path"] = str(style_file)
if plot_resolution:
recipe["plot_resolution"] = plot_resolution
_write_metadata(recipe)
# Execute the recipe.
for step in steps:
Expand All @@ -174,6 +183,7 @@ def execute_recipe_parallel(
output_directory: Path,
recipe_variables: dict = None,
style_file: Path = None,
plot_resolution: int = None,
) -> None:
"""Parse and executes the parallel steps from a recipe file.
Expand All @@ -188,8 +198,12 @@ def execute_recipe_parallel(
input.
output_directory: Path
Pathlike indicating desired location of output.
recipe_variables: dict
recipe_variables: dict, optional
Dictionary of variables for the recipe.
style_file: Path, optional
Path to a style file.
plot_resolution: int, optional
Resolution of plots in dpi.
Raises
------
Expand All @@ -213,14 +227,15 @@ def execute_recipe_parallel(
logging.error("Output directory is a file. %s", output_directory)
raise err
steps = recipe["parallel"]
_run_steps(recipe, steps, step_input, output_directory, style_file)
_run_steps(recipe, steps, step_input, output_directory, style_file, plot_resolution)


def execute_recipe_collate(
recipe_yaml: Union[Path, str],
output_directory: Path,
recipe_variables: dict = None,
style_file: Path = None,
plot_resolution: int = None,
) -> None:
"""Parse and execute the collation steps from a recipe file.
Expand All @@ -234,6 +249,10 @@ def execute_recipe_collate(
Pathlike indicating desired location of output. Must already exist.
recipe_variables: dict
Dictionary of variables for the recipe.
style_file: Path, optional
Path to a style file.
plot_resolution: int, optional
Resolution of plots in dpi.
Raises
------
Expand All @@ -249,4 +268,6 @@ def execute_recipe_collate(
recipe = parse_recipe(recipe_yaml, recipe_variables)
# If collate doesn't exist treat it as having no steps.
steps = recipe.get("collate", [])
_run_steps(recipe, steps, output_directory, output_directory, style_file)
_run_steps(
recipe, steps, output_directory, output_directory, style_file, plot_resolution
)
21 changes: 13 additions & 8 deletions src/CSET/operators/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ def _colorbar_map_levels(varname: str, **kwargs):
return cmap, levels, norm


def _get_plot_resolution() -> int:
"""Get resolution of rasterised plots in pixels per inch."""
return get_recipe_metadata().get("plot_resolution", 100)


def _plot_and_save_contour_plot(
cube: iris.cube.Cube,
filename: str,
Expand Down Expand Up @@ -248,7 +253,7 @@ def _plot_and_save_contour_plot(
cbar.set_label(label=f"{cube.name()} ({cube.units})", size=20)

# Save plot.
fig.savefig(filename, bbox_inches="tight", dpi=100)
fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())
logging.info("Saved contour plot to %s", filename)
plt.close(fig)

Expand Down Expand Up @@ -311,7 +316,7 @@ def _plot_and_save_postage_stamp_contour_plot(
# Overall figure title.
fig.suptitle(title)

fig.savefig(filename, bbox_inches="tight", dpi=100)
fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())
logging.info("Saved contour postage stamp plot to %s", filename)
plt.close(fig)

Expand Down Expand Up @@ -347,7 +352,7 @@ def _plot_and_save_line_series(
ax.autoscale()

# Save plot.
fig.savefig(filename, bbox_inches="tight", dpi=100)
fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())
logging.info("Saved line plot to %s", filename)
plt.close(fig)

Expand Down Expand Up @@ -436,7 +441,7 @@ def _plot_and_save_vertical_line_series(
ax.autoscale()

# Save plot.
fig.savefig(filename, bbox_inches="tight", dpi=100)
fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())
logging.info("Saved line plot to %s", filename)
plt.close(fig)

Expand Down Expand Up @@ -492,7 +497,7 @@ def _plot_and_save_scatter_plot(
ax.autoscale()

# Save plot.
fig.savefig(filename, bbox_inches="tight", dpi=100)
fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())
logging.info("Saved scatter plot to %s", filename)
plt.close(fig)

Expand Down Expand Up @@ -551,7 +556,7 @@ def _plot_and_save_histogram_series(
)

# Save plot.
fig.savefig(filename, bbox_inches="tight", dpi=100)
fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())
logging.info("Saved line plot to %s", filename)
plt.close(fig)

Expand Down Expand Up @@ -620,7 +625,7 @@ def _plot_and_save_postage_stamp_histogram_series(
# Overall figure title.
fig.suptitle(title)

fig.savefig(filename, bbox_inches="tight", dpi=100)
fig.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())
logging.info("Saved histogram postage stamp plot to %s", filename)
plt.close(fig)

Expand Down Expand Up @@ -658,7 +663,7 @@ def _plot_and_save_postage_stamps_in_single_plot_histogram_series(
ax.legend()

# Save the figure to a file
plt.savefig(filename, bbox_inches="tight", dpi=100)
plt.savefig(filename, bbox_inches="tight", dpi=_get_plot_resolution())

# Close the figure
plt.close(fig)
Expand Down
14 changes: 14 additions & 0 deletions tests/operators/test_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,17 @@ def test_scatter_plot_too_many_y_dimensions(
cube_x = collapse.collapse(vertical_profile_cube, ["time"], "MEAN")[0:4]
with pytest.raises(ValueError):
plot.scatter_plot(cube_x, cube_y)


def test_get_plot_resolution(tmp_working_dir):
"""Test getting the plot resolution."""
with open("meta.json", "wt", encoding="UTF-8") as fp:
fp.write('{"plot_resolution": 72}')
resolution = plot._get_plot_resolution()
assert resolution == 72


def test_get_plot_resolution_unset(tmp_working_dir):
"""Test getting the default plot resolution when unset."""
resolution = plot._get_plot_resolution()
assert resolution == 100
8 changes: 8 additions & 0 deletions tests/test_run_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,11 @@ def test_run_steps_style_file_metadata_written(tmp_path: Path):
with open(tmp_path / "meta.json", "rb") as fp:
metadata = json.load(fp)
assert metadata["style_file_path"] == style_file_path


def test_run_steps_plot_resolution_metadata_written(tmp_path: Path):
"""Style file path metadata written out."""
CSET.operators._run_steps({}, [], None, tmp_path, plot_resolution=72)
with open(tmp_path / "meta.json", "rb") as fp:
metadata = json.load(fp)
assert metadata["plot_resolution"] == 72

0 comments on commit 7dfd564

Please # to comment.