Source code for improver.metadata.probabilistic
# (C) Crown Copyright, Met Office. All rights reserved.
#
# This file is part of 'IMPROVER' and is released under the BSD 3-Clause license.
# See LICENSE in the root of the repository for full licensing details.
"""Utilities for interrogating IMPROVER probabilistic metadata"""
import re
from typing import Match, Optional
import iris
from iris.coords import Coord
from iris.cube import Cube
from iris.exceptions import CoordinateNotFoundError
[docs]
def probability_cube_name_regex(cube_name: str) -> Optional[Match]:
"""
Regular expression matching IMPROVER probability cube name. Returns
None if the cube_name does not match the regular expression (ie does
not start with 'probability_of').
Args:
cube_name:
Probability cube name
Returns:
The regex match
"""
regex = re.compile(
"(probability_of_)" # always starts this way
"(?P<diag>.*?)" # named group for the diagnostic name
"(?P<vicinity>_in_vicinity|_in_variable_vicinity)?" # optional group, may be empty
"(?P<thresh>_above_threshold|_below_threshold|_between_thresholds|$)"
)
return regex.match(cube_name)
[docs]
def get_threshold_coord_name_from_probability_name(cube_name: str) -> str:
"""Get the name of the threshold coordinate from the name of the probability
cube. This can be used to set or modify a threshold coordinate name after
renaming or conversion from probabilities to percentiles / realizations."""
return _extract_diagnostic_name(cube_name)
[docs]
def get_diagnostic_cube_name_from_probability_name(cube_name: str) -> str:
"""Get the name of the original diagnostic cube, including vicinity, from
the name of the probability cube."""
return _extract_diagnostic_name(cube_name, check_vicinity=True)
[docs]
def is_probability(cube: Cube) -> bool:
"""Determines whether a cube contains probability data at a range of
thresholds.
Args:
cube:
Cube to check for probability threshold data.
Returns:
True if in threshold representation.
"""
try:
find_threshold_coordinate(cube)
except CoordinateNotFoundError:
return False
return True
[docs]
def find_threshold_coordinate(cube: Cube) -> Coord:
"""Find threshold coordinate in cube.
Compatible with both the old (cube.coord("threshold")) and new
(cube.coord.var_name == "threshold") IMPROVER metadata standards.
Args:
cube:
Cube containing thresholded probability data
Returns:
Threshold coordinate
Raises:
TypeError: If cube is not of type iris.cube.Cube.
CoordinateNotFoundError: If no threshold coordinate is found.
"""
if not isinstance(cube, iris.cube.Cube):
msg = (
"Expecting data to be an instance of " "iris.cube.Cube but is {0}.".format(
type(cube)
)
)
raise TypeError(msg)
threshold_coord = None
try:
threshold_coord = cube.coord("threshold")
except CoordinateNotFoundError:
for coord in cube.coords():
if coord.var_name == "threshold":
threshold_coord = coord
break
if threshold_coord is None:
msg = "No threshold coord found on {0:s} data".format(cube.name())
raise CoordinateNotFoundError(msg)
return threshold_coord
[docs]
def probability_is_above_or_below(cube: Cube) -> Optional[str]:
"""Checks the spp__relative_to_threshold attribute and outputs
whether it is above or below the threshold given. If there isn't
a spp__relative_to_threshold attribute it returns None.
Args:
cube:
Cube containing thresholded probability data
Returns:
Which indicates whether the cube has data that is
above or below the threshold
"""
threshold_attribute = None
thresh_coord = find_threshold_coordinate(cube)
thresh = thresh_coord.attributes.get("spp__relative_to_threshold", None)
if thresh in ("above", "greater_than", "greater_than_or_equal_to"):
threshold_attribute = "above"
elif thresh in ("below", "less_than", "less_than_or_equal_to"):
threshold_attribute = "below"
return threshold_attribute
[docs]
def is_percentile(cube: Cube) -> bool:
"""Determines whether a cube contains probability data at a range of
percentiles.
Args:
cube:
Cube to check for percentile data.
Returns:
True if in percentile representation.
"""
try:
find_percentile_coordinate(cube)
except (CoordinateNotFoundError, ValueError):
return False
return True
[docs]
def find_percentile_coordinate(cube: Cube) -> Coord:
"""Find percentile coord in cube.
Args:
cube:
Cube contain one or more percentiles.
Returns:
Percentile coordinate.
Raises:
TypeError: If cube is not of type iris.cube.Cube.
CoordinateNotFoundError: If no percentile coordinate is found in cube.
ValueError: If there is more than one percentile coords in the cube.
"""
if not isinstance(cube, iris.cube.Cube):
msg = (
"Expecting data to be an instance of " "iris.cube.Cube but is {0}.".format(
type(cube)
)
)
raise TypeError(msg)
standard_name = cube.name()
perc_coord = None
perc_found = 0
for coord in cube.coords():
if coord.name().find("percentile") >= 0:
perc_found += 1
perc_coord = coord
if perc_found == 0:
msg = "No percentile coord found on {0:s} data".format(standard_name)
raise CoordinateNotFoundError(msg)
if perc_found > 1:
msg = "Too many percentile coords found on {0:s} data".format(standard_name)
raise ValueError(msg)
return perc_coord