Source code for iris.nodes.iris_response.probe_schemas.regular_probe_schema

from typing import List, Literal, Optional, Tuple, Union

import numpy as np
from pydantic import Field, PositiveInt, confloat, fields, validator

from iris.io.errors import ProbeSchemaError
from iris.nodes.iris_response.probe_schemas.probe_schema_interface import ProbeSchema


[docs]class RegularProbeSchema(ProbeSchema): """Probe Schema for a regular Grid."""
[docs] class RegularProbeSchemaParameters(ProbeSchema.ProbeSchemaParameters): """RegularProbeSchema parameters.""" n_rows: int = Field(..., gt=1) n_cols: int = Field(..., gt=1) boundary_rho: List[confloat(ge=0.0, lt=1)] boundary_phi: Union[ Literal["periodic-symmetric", "periodic-left"], List[confloat(ge=0.0, lt=1)], ] image_shape: Optional[List[PositiveInt]]
[docs] @validator("boundary_rho", "boundary_phi") def check_overlap( cls: type, v: Union[Literal["periodic-symmetric", "periodic-left"], List[confloat(ge=0.0, lt=1)]], field: fields.ModelField, ) -> Union[Literal["periodic-symmetric", "periodic-left"], List[confloat(ge=0.0, lt=1)]]: """Validate offsets to avoid overlap. Args: cls (type): Class type. v (Union[Literal["periodic-symmetric", "periodic-left"], List[confloat(ge=0.0, lt=1)]]): Value to check. field (fields.ModelField): Field descriptor. Raises: ProbeSchemaError: Raises warning that offsets are together too large. Returns: Union[Literal["periodic-symmetric", "periodic-left"], List[confloat(ge=0.0, lt=1)]]: The value for boundary_rho or boundary_phi respectively """ if isinstance(v, List): if (v[0] + v[1]) >= 1: raise ProbeSchemaError( f"Offset for {field.name} on left and right corner must be a sum smaller 1, otherwise, offsets overlap." ) return v
__parameters_type__ = RegularProbeSchemaParameters def __init__( self, n_rows: int, n_cols: int, boundary_rho: List[float] = [0, 0.0625], boundary_phi: Union[ Literal["periodic-symmetric", "periodic-left"], List[confloat(ge=0.0, lt=1)] ] = "periodic-left", image_shape: Optional[List[PositiveInt]] = None, ) -> None: """Assign parameters. Args: n_rows (int): Number of rows used, represents the number of different rho values n_cols (int): Number of columns used, represents the number of different phi values boundary_rho (List[float], optional): List with two values f1 and f2. The sampling goes from 0+f1 to 0-f2. boundary_phi (Union[Literal["periodic-symmetric", "periodic-left"], List[confloat(ge=0.0, lt=1)]], optional): Boundary conditions for the probing can either be periodic or non-periodic, if they are periodic, the distance from one column to the next must be the same also for the boundaries. Else, no conditions for the boundaries are required. Options are: - 'periodic-symmetric': the first and the last column are placed with an offset to the borders, that is half of the spacing of the two columns - 'periodic-left': the first column is at the border of the bottom of the image, while the last column is one spacing apart from the top of the image - list with two values: in this case the an offset of value f1 and f2 is set on both ends, i.e. the the sampling no longer goes from 0 to 1 ('no-offset') but instead from 0+f1 to 0-f2 Defaults to "periodic_symmetric". image_shape (list, optional): list containing the desired image dimensions. If provided, the function will throw a warning if interpolation happens, i.e. if a kernel would be placed in between two pixels. Defaults to None. """ super().__init__( n_rows=n_rows, n_cols=n_cols, boundary_rho=boundary_rho, boundary_phi=boundary_phi, image_shape=image_shape, )
[docs] def generate_schema(self) -> Tuple[np.ndarray, np.ndarray]: """Generate rhos and phis. Return: Tuple[np.ndarray, np.ndarray]: the rhos and phis. """ rho = np.linspace( 0 + self.params.boundary_rho[0], 1 - self.params.boundary_rho[1], self.params.n_rows, endpoint=True ) if self.params.boundary_phi == "periodic-symmetric": phi = np.linspace(0, 1, self.params.n_cols, endpoint=False) phi = phi + (phi[1] - phi[0]) / 2 if self.params.boundary_phi == "periodic-left": phi = np.linspace(0, 1, self.params.n_cols, endpoint=False) if isinstance(self.params.boundary_phi, List): phi = np.linspace( 0 + self.params.boundary_phi[0], 1 - self.params.boundary_phi[1], self.params.n_cols, endpoint=True ) phis, rhos = np.meshgrid(phi, rho) rhos = rhos.flatten() phis = phis.flatten() # if image_shape provided: verify that values lie on pixel values if self.params.image_shape is not None: rhos_pixel_values = rhos * self.params.image_shape[0] phis_pixel_values = phis * self.params.image_shape[1] rho_pixel_values = np.logical_or( np.less_equal(rhos_pixel_values % 1, 10 ** (-10)), np.less_equal(1 - 10 ** (-10), rhos_pixel_values % 1), ).all() phi_pixel_values = np.logical_or( np.less_equal(phis_pixel_values % 1, 10 ** (-10)), np.less_equal(1 - 10 ** (-10), phis_pixel_values % 1), ).all() if not rho_pixel_values: raise ProbeSchemaError( f"Choice for n_rows {self.params.n_rows} leads to interpolation errors, please change input variables" ) if not phi_pixel_values: raise ProbeSchemaError(f"Choice for n_cols {self.params.n_cols} leads to interpolation errors") return rhos, phis
[docs] @staticmethod def find_suitable_n_rows( row_min: int, row_max: int, length: int, boundary_condition: Union[ Literal["periodic-symmetric", "periodic-left"], List[float], ] = "periodic_symmetric", ) -> List[int]: """Find proper spacing of rows/columns for given boundary conditions (i.e. image size, offset. etc). Args: row_min (int): Starting value for row count row_max (int): End value for row count length (int): Pixels in the respective dimension boundary_condition (Union[Literal["periodic-symmetric", "periodic-left"], List[float]], optional): Boundary conditions for the probing can either be periodic or non-periodic, if they are periodic, the distance from one row to the next must be the same also for the boundaries. Defaults to "periodic_symmetric". Else, no conditions for the boundaries are required. Options are: - 'periodic-symmetric': the first and the last row are placed with an offset to the borders, that is half of the spacing of the two rows - 'periodic-left': the first row is at the border of the bottom of the image, while the last row is one spacing apart from the top of the image - list with two values: in this case the an offset of value f1 and f2 is set on both ends, i.e. the the sampling no longer goes from 0 to 1 ('no-offset') but instead from 0+f1 to 0-f2 Returns: List[int]: List of all number of rows that does not lead to interpolation errors """ suitable_values: List[int] = [] # loop through all values and validate whether they are suitable for counter in range(row_min, row_max + 1): if boundary_condition == "periodic-symmetric": values = np.linspace(0, 1, counter, endpoint=False) values = values + (values[1] - values[0]) / 2 if boundary_condition == "periodic-left": values = np.linspace(0, 1, counter, endpoint=False) if isinstance(boundary_condition, List): values = np.linspace(0 + boundary_condition[0], 1 - boundary_condition[1], counter, endpoint=True) pixel_values = values * length pixel_values_modulo = pixel_values % 1 no_interpolation = np.less_equal(pixel_values_modulo, 10 ** (-10)) no_interpolation = np.logical_or(no_interpolation, np.less_equal(1 - 10 ** (-10), pixel_values_modulo)) no_interpolation = no_interpolation.all() if no_interpolation: suitable_values.append(counter) return suitable_values