Source code for iris.nodes.geometry_estimation.linear_extrapolation
from typing import List, Tuple
import numpy as np
from pydantic import Field
from iris.callbacks.callback_interface import Callback
from iris.io.class_configs import Algorithm
from iris.io.dataclasses import EyeCenters, GeometryPolygons
from iris.utils import math
[docs]class LinearExtrapolation(Algorithm):
"""Implementation of geometry estimation algorithm through linear extrapolation in polar space.
Algorithm depends on np.interp therefore it's better to perform smoothing beforehand.
Algorith steps:
1) Map iris/pupil polygon vertices to polar space based on estimated iris/pupil centers.
2) For iris/pupil, perform function interpolation in polar space to estimate missing circle points.
Note: interpolation in polar space is extrapolation in cartesian space.
3) Take 2 * np.pi / dphi points from function in polar space.
4) Map iris/pupil points from polar space back to cartesian space.
"""
[docs] class Parameters(Algorithm.Parameters):
"""Parameters of linear extrapolation algorithm."""
dphi: float = Field(..., gt=0.0, lt=360.0)
__parameters_type__ = Parameters
def __init__(self, dphi: float = 0.9, callbacks: List[Callback] = []) -> None:
"""Assign parameters.
Args:
dphi (float, optional): phi angle delta used to sample points while doing smoothing by interpolation. Defaults to 0.9.
callbacks (List[Callback]): callbacks list. Defaults to [].
"""
super().__init__(dphi=dphi, callbacks=callbacks)
[docs] def run(self, input_polygons: GeometryPolygons, eye_center: EyeCenters) -> GeometryPolygons:
"""Estimate contours.
Args:
input_polygons (GeometryPolygons): Input contours.
eye_center (EyeCenters): Eye's centers.
Returns:
GeometryPolygons: Extrapolated contours.
"""
estimated_pupil = self._estimate(input_polygons.pupil_array, (eye_center.pupil_x, eye_center.pupil_y))
estimated_iris = self._estimate(input_polygons.iris_array, (eye_center.iris_x, eye_center.iris_y))
return GeometryPolygons(
pupil_array=estimated_pupil, iris_array=estimated_iris, eyeball_array=input_polygons.eyeball_array
)
def _estimate(self, vertices: np.ndarray, center_xy: Tuple[float, float]) -> np.ndarray:
"""Estimate a circle fit for a single contour.
Args:
vertices (np.ndarray): Contour's vertices.
center_xy (Tuple[float, float]): Contour's center position.
Returns:
np.ndarray: Estimated polygon.
"""
rhos, phis = math.cartesian2polar(vertices[:, 0], vertices[:, 1], *center_xy)
padded_rhos = np.concatenate([rhos, rhos, rhos])
padded_phis = np.concatenate([phis - 2 * np.pi, phis, phis + 2 * np.pi])
interpolated_phis = np.arange(padded_phis.min(), padded_phis.max(), np.radians(self.params.dphi))
interpolated_rhos = np.interp(interpolated_phis, xp=padded_phis, fp=padded_rhos, period=2 * np.pi)
mask = (interpolated_phis >= 0) & (interpolated_phis < 2 * np.pi)
interpolated_phis, interpolated_rhos = interpolated_phis[mask], interpolated_rhos[mask]
xs, ys = math.polar2cartesian(interpolated_rhos, interpolated_phis, *center_xy)
estimated_vertices = np.column_stack([xs, ys])
return estimated_vertices