Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Configurable plotting resolution #803

Merged
merged 1 commit into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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