Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5e5e8c0
Create module for testing plots
LydiaFrance Sep 19, 2025
3449066
Dummy arctic and SIC data for test plots
LydiaFrance Sep 19, 2025
ffd54f9
Restore comment
LydiaFrance Sep 19, 2025
d393123
Dummy SIC data stream
LydiaFrance Sep 19, 2025
d98285f
Switch to making stream only for simplification, single frame is extr…
LydiaFrance Sep 19, 2025
c98003f
Comment out optional code for seeing mask
LydiaFrance Sep 24, 2025
99497b1
Comment out optional code for seeing mask
LydiaFrance Sep 24, 2025
71f1141
Option for matplotlib
LydiaFrance Sep 24, 2025
9241c24
fix imports and exceptions
LydiaFrance Sep 24, 2025
a686c46
Test plot spec
LydiaFrance Sep 24, 2025
823e64c
Improve layout
LydiaFrance Sep 24, 2025
fa2c5af
Fix file name for helper file
LydiaFrance Sep 24, 2025
cb7e52d
tests for differencing and colour
LydiaFrance Sep 24, 2025
d2de360
Tests the plot subplots layout
LydiaFrance Sep 24, 2025
4e97dd7
Test e2e making images and video
LydiaFrance Sep 24, 2025
a8dc43e
Fix mp4 dimension issue
LydiaFrance Sep 24, 2025
66b2340
Improve plot layouts
LydiaFrance Sep 24, 2025
cde7f07
Include small checkpoint for testing
LydiaFrance Sep 24, 2025
befe617
Flag issues near title
LydiaFrance Oct 1, 2025
8bcb3b6
Sanity checks for colourscale
LydiaFrance Oct 1, 2025
46589fa
Move to separate file
LydiaFrance Oct 1, 2025
c8fba90
Update plot spec
LydiaFrance Oct 1, 2025
8be8840
sanity report for init
LydiaFrance Oct 1, 2025
96a777d
Make ticks clearer
LydiaFrance Oct 1, 2025
a10e1ae
Refer to plot spec from types
LydiaFrance Oct 2, 2025
738c466
Merge branch 'main' into 86-plot-tests
LydiaFrance Oct 2, 2025
9560ce2
Sort import fix
LydiaFrance Oct 2, 2025
82bdfd6
Fix for CI testing video writer
LydiaFrance Oct 2, 2025
4eb84f1
Fix docstrings
LydiaFrance Oct 2, 2025
55604e9
More comments
LydiaFrance Oct 2, 2025
83e34e3
More comments
LydiaFrance Oct 2, 2025
3aa403e
Fix for video arg in tests
LydiaFrance Oct 2, 2025
e0d3225
Fix explanation
LydiaFrance Oct 2, 2025
e3eb8c8
Fix for animation test
LydiaFrance Oct 2, 2025
6fe32bf
Fix for mypy
LydiaFrance Oct 2, 2025
e3f84e6
Fixes for mypy
LydiaFrance Oct 2, 2025
e7b3980
Put back commented out precommit hook instruction
LydiaFrance Oct 3, 2025
c872429
Merge branch 'main' into 86-plot-tests
LydiaFrance Oct 8, 2025
97bbaad
Fix matplotlib animation warning
LydiaFrance Oct 8, 2025
536b2a8
Merge branch '86-plot-tests' of https://github.com/alan-turing-instit…
LydiaFrance Oct 8, 2025
c492f25
switch to dfferent plotting option
LydiaFrance Oct 8, 2025
fd788b8
Fix for mypy figure attribute
LydiaFrance Oct 8, 2025
6c2e286
Fix for ruff figure attribute
LydiaFrance Oct 8, 2025
88a56fd
Move fake arctic fixture
LydiaFrance Oct 8, 2025
af2546c
Ruff fix for animation deletion warning
LydiaFrance Oct 8, 2025
5cb84cc
Name change
LydiaFrance Oct 8, 2025
974d46b
Change name to fake data
LydiaFrance Oct 8, 2025
a8c10aa
init file
LydiaFrance Oct 8, 2025
2cc8029
Changed testing strategy to more explicit scenarios
LydiaFrance Oct 9, 2025
c6ec511
Fix cast
LydiaFrance Oct 9, 2025
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
4 changes: 2 additions & 2 deletions ice_station_zebra/config/evaluate/callbacks/plotting.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ plotting:
diff_strategy: "precompute" # "precompute" | "two-pass" | "per-frame"
vmin: 0.0
vmax: 1.0
colourbar_location: "vertical"
colourbar_strategy: "separate" # shared | separate (prediction gets its own colourbar)
colourbar_location: "horizontal"
colourbar_strategy: "shared" # shared | separate (prediction gets its own colourbar)
51 changes: 41 additions & 10 deletions ice_station_zebra/types/simple_datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ class DataloaderArgs(TypedDict):


class DiffColourmapSpec(NamedTuple):
"""Colour scale specification for visualising a difference panel.
"""Specify the colour scale used for a difference panel.

Attributes:
norm: Normalisation object for mapping data values to colours.
Typically TwoSlopeNorm for signed differences (centred at 0).
None for non-signed modes (absolute, smape).
vmin: Lower bound for colour scale (used if norm is None).
vmax: Upper bound for colour scale (used if norm is None).
cmap: Name of the matplotlib colourmap to use.
norm: Normalisation for mapping values to colours (e.g. TwoSlopeNorm for signed diffs).
vmin: Lower bound if no norm is provided.
vmax: Upper bound if no norm is provided.
cmap: Matplotlib colormap name.

"""

Expand All @@ -63,19 +61,52 @@ class DiffColourmapSpec(NamedTuple):

@dataclass(frozen=True)
class PlotSpec:
"""Specification for a plotting strategy."""
"""Configure how sea-ice plots are rendered.

Attributes:
variable: Variable name shown in plots / used for routing.
title_groundtruth: Title above the ground-truth panel.
title_prediction: Title above the prediction panel.
title_difference: Title above the difference panel.
n_contour_levels: Number of contour levels per panel.
colourmap: Colormap used for GT/prediction panels.
include_difference: Whether to draw a difference panel.
diff_mode: Difference definition (e.g. "signed", "absolute", "smape").
diff_strategy: Strategy for animations (precompute, two-pass, per-frame).
selected_timestep: Slice index when a single timestep is needed.
vmin: Lower bound for GT/prediction colour scale (None = infer).
vmax: Upper bound for GT/prediction colour scale (None = infer).
colourbar_location: "vertical" or "horizontal".
colourbar_strategy: "shared" or "separate" colourbars.
outside_warn: Threshold for “values outside display range” warnings.
severe_outside: Severe threshold for clipping warnings.
include_shared_range_mismatch_check: If True, add magnitude mismatch nudges.

"""

variable: str
title_groundtruth: str = "Ground Truth"
title_prediction: str = "Prediction"
title_difference: str = "Difference"
n_contour_levels: int = 100

n_contour_levels: int = 51
colourmap: str = "viridis"

# Difference pane
include_difference: bool = True
diff_mode: DiffMode = "signed"
diff_strategy: DiffStrategy = "precompute"
selected_timestep: int = 0

# Colourscale ranges: defaults to [0,1]
vmin: float | None = 0.0
vmax: float | None = 1.0
colourbar_location: Literal["vertical", "horizontal"] = "vertical"

# Colourbar layout
colourbar_location: Literal["vertical", "horizontal"] = "horizontal"
colourbar_strategy: Literal["shared", "separate"] = "shared"

# Range Check/warnings in badge
outside_warn: float = 0.05
severe_outside: float = 0.20
include_shared_range_mismatch_check: bool = True
5 changes: 4 additions & 1 deletion ice_station_zebra/visualisations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from .plotting_core import PlotSpec
from ice_station_zebra.types import PlotSpec

from .plotting_maps import (
DEFAULT_SIC_SPEC,
plot_maps,
video_maps,
)
from .range_check import compute_range_check_report

__all__ = [
"DEFAULT_SIC_SPEC",
"PlotSpec",
"compute_range_check_report",
"plot_maps",
"video_maps",
]
21 changes: 17 additions & 4 deletions ice_station_zebra/visualisations/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,35 @@ def _image_from_array(a: np.ndarray) -> ImageFile:
def _save_animation(
anim: animation.FuncAnimation,
*,
fps: int,
fps: int | None = None,
video_format: Literal["mp4", "gif"] = "gif",
_fps: int | None = None,
_video_format: Literal["mp4", "gif"] | None = None,
) -> io.BytesIO:
"""Save an animation to a temporary file and return BytesIO (with cleanup)."""
# Accept both standard and underscored names for test compatibility
fps_value: int = int(fps if fps is not None else (_fps if _fps is not None else 2))
if _video_format is not None:
video_format = _video_format # prefer underscored override if provided
suffix = ".gif" if video_format.lower() == "gif" else ".mp4"
with tempfile.NamedTemporaryFile(suffix=suffix, delete=True) as tmp:
try:
# Save video to tempfile
writer: animation.AbstractMovieWriter = (
animation.PillowWriter(fps=fps)
animation.PillowWriter(fps=fps_value)
if suffix == ".gif"
else animation.FFMpegWriter(
fps=fps,
fps=fps_value,
codec="libx264",
bitrate=1800,
extra_args=["-pix_fmt", "yuv420p"],
# Ensure dimensions are compatible with yuv420p (even width/height)
# by applying a scale filter that truncates to the nearest even integers.
extra_args=[
"-pix_fmt",
"yuv420p",
"-vf",
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
],
)
)
anim.save(tmp.name, writer=writer, dpi=DEFAULT_DPI)
Expand Down
Loading
Loading