Source code for hats.pixel_math.validators
from __future__ import annotations
from enum import Enum
import numpy as np
import hats.pixel_math.healpix_shim as hp
[docs]
class ValidatorsErrors(str, Enum):
"""Error messages for the coordinate validators"""
[docs]
INVALID_DEC = "declination must be in the -90.0 to 90.0 degree range"
[docs]
INVALID_RADIUS = "cone radius must be positive"
[docs]
INVALID_NUM_VERTICES = "polygon must contain a minimum of 3 vertices"
[docs]
DUPLICATE_VERTICES = "polygon has duplicated vertices"
[docs]
DEGENERATE_POLYGON = "polygon is degenerate"
[docs]
INVALID_RADEC_RANGE = "invalid ra or dec range"
[docs]
INVALID_COORDS_SHAPE = "invalid coordinates shape"
[docs]
INVALID_CONCAVE_SHAPE = "polygon must be convex"
[docs]
def validate_radius(radius_arcsec: float):
"""Validates that a cone search radius is positive
Parameters
----------
radius_arcsec : float
The cone radius, in arcseconds
Raises
------
ValueError
if radius is non-positive
"""
if radius_arcsec <= 0:
raise ValueError(ValidatorsErrors.INVALID_RADIUS.value)
[docs]
def validate_declination_values(dec: float | list[float]):
"""Validates that declination values are in the [-90,90] degree range
Parameters
----------
dec : float | list[float]
The declination values to be validated
Raises
------
ValueError
if declination values are not in the [-90,90] degree range
"""
dec_values = np.array(dec)
lower_bound, upper_bound = -90.0, 90.0
if not np.all((dec_values >= lower_bound) & (dec_values <= upper_bound)):
raise ValueError(ValidatorsErrors.INVALID_DEC.value)
[docs]
def validate_polygon(vertices: list[tuple[float, float]]):
"""Checks if the polygon contain a minimum of three vertices, that they are
unique and that the polygon does not fall on a great circle.
Parameters
----------
vertices : list[tuple[float, float]]
The list of vertice coordinates for the polygon, (ra, dec), in degrees.
Raises
------
ValueError
exception if the polygon is invalid.
"""
vertices = np.array(vertices)
if vertices.shape[1] != 2:
raise ValueError(ValidatorsErrors.INVALID_COORDS_SHAPE.value)
_, dec = vertices.T
validate_declination_values(dec)
if len(vertices) < 3:
raise ValueError(ValidatorsErrors.INVALID_NUM_VERTICES.value)
if len(vertices) != len(np.unique(vertices, axis=0)):
raise ValueError(ValidatorsErrors.DUPLICATE_VERTICES.value)
check_polygon_is_valid(vertices)
[docs]
def check_polygon_is_valid(vertices: np.ndarray):
"""Check if the polygon has no degenerate corners and it is convex.
Parameters
----------
vertices : np.ndarray
The polygon vertices, in cartesian coordinates
Raises
------
ValueError
exception if the polygon is invalid.
"""
vertices_xyz = hp.ang2vec(*vertices.T)
# Compute the normal between each pair of neighboring vertices
second_vertices = np.roll(vertices_xyz, -1, axis=0)
normals = np.cross(vertices_xyz, second_vertices)
# Compute the dot products between each normal and a third neighboring vertex.
# 'ij,ij->i' means we will multiply each normal vector with the corresponding
# vector of the third vertex ('ij,ij': hence element-wise) and sum over each
# column for each row '->i'.
third_vertices = np.roll(second_vertices, -1, axis=0)
dot_products = np.einsum("ij,ij->i", normals, third_vertices)
if np.any(np.isclose(dot_products, 0)):
raise ValueError(ValidatorsErrors.DEGENERATE_POLYGON.value)
if not (np.all(dot_products > 0) or np.all(dot_products < 0)):
raise ValueError(ValidatorsErrors.INVALID_CONCAVE_SHAPE.value)
[docs]
def validate_box(ra: tuple[float, float], dec: tuple[float, float]):
"""Checks if ra and dec values are valid for the box search.
- Both ranges for ra or dec must have been provided.
- Ranges must be defined by a pair of values, in degrees.
- Declination values must be unique, provided in ascending order, and belong to
the [-90,90] degree range.
Parameters
----------
ra : tuple[float, float]
Right ascension range, in degrees
dec : tuple[float, float]
Declination range, in degrees
Raises
------
ValueError
exception if the box is invalid.
"""
invalid_range = False
if ra is None or len(ra) != 2:
invalid_range = True
elif dec is None or len(dec) != 2 or dec[0] >= dec[1]:
invalid_range = True
if invalid_range:
raise ValueError(ValidatorsErrors.INVALID_RADEC_RANGE.value)
validate_declination_values(dec)