Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fb93446
tets workflow
ShevchenkoRostyslav Jul 21, 2025
5cc52d2
upd
ShevchenkoRostyslav Jul 21, 2025
ff59988
add prod workflow
ShevchenkoRostyslav Jul 21, 2025
3dcbb05
correct the repo name
ShevchenkoRostyslav Jul 21, 2025
32f62cb
upd name
ShevchenkoRostyslav Jul 21, 2025
f349dff
add a workflow to create docs
ShevchenkoRostyslav Jul 21, 2025
26acfca
address PR comments
ShevchenkoRostyslav Jul 21, 2025
d68b6a4
debugging
ShevchenkoRostyslav Jul 21, 2025
9ff57d0
add requirements
ShevchenkoRostyslav Jul 21, 2025
a8f4cfc
upd deploy docs
ShevchenkoRostyslav Jul 21, 2025
c1c2601
docs branch
ShevchenkoRostyslav Jul 21, 2025
b533b3a
fix bug
ShevchenkoRostyslav Jul 21, 2025
dddd316
cleaning
ShevchenkoRostyslav Jul 21, 2025
23f7d9a
new way
ShevchenkoRostyslav Jul 21, 2025
af97b8a
fake upd of docds
ShevchenkoRostyslav Jul 21, 2025
070a4cd
upd
ShevchenkoRostyslav Jul 21, 2025
1ed8879
upd
ShevchenkoRostyslav Jul 21, 2025
3902e05
rollback
ShevchenkoRostyslav Jul 21, 2025
15adee6
upd
ShevchenkoRostyslav Jul 21, 2025
a7bdb8f
upd
ShevchenkoRostyslav Jul 21, 2025
5cbc97c
revert
ShevchenkoRostyslav Jul 21, 2025
615003f
worktree
ShevchenkoRostyslav Jul 21, 2025
29ac216
another approach
ShevchenkoRostyslav Jul 21, 2025
5110cc7
upd
ShevchenkoRostyslav Jul 21, 2025
7018afb
keep some files
ShevchenkoRostyslav Jul 21, 2025
bf0389f
final version
ShevchenkoRostyslav Jul 21, 2025
d07f85c
swap the order
ShevchenkoRostyslav Jul 21, 2025
89a32e0
upd
ShevchenkoRostyslav Jul 21, 2025
6229e2e
upd
ShevchenkoRostyslav Jul 21, 2025
e61d815
upd
ShevchenkoRostyslav Jul 21, 2025
5ae9407
remove .buildinfo
ShevchenkoRostyslav Jul 22, 2025
0142f92
remove .buildinfo
ShevchenkoRostyslav Jul 22, 2025
1a73956
cleaning
ShevchenkoRostyslav Jul 22, 2025
017ca81
Merge pull request #124 from worldcoin/tech_debt/AIC-5935
ShevchenkoRostyslav Jul 22, 2025
bcf0f98
Iris multiview/aic 5901: e2e Multiframe Iris Pipeline (#123)
ShevchenkoRostyslav Aug 4, 2025
ae4ce4a
1.8.3 -> 1.9.0 (#137)
ShevchenkoRostyslav Aug 4, 2025
4441fcd
Make sure that NodeWritterResult callback is always first in callback…
wiktorlazarski Aug 6, 2025
be997b5
Merge pull request #138 from worldcoin/wiktorlazarski/bug-fix-node-wr…
wiktorlazarski Aug 6, 2025
28e5d44
Hotfix/output builders type (#139)
ShevchenkoRostyslav Aug 19, 2025
aae6d82
Hotfix/output builders type (#141)
ShevchenkoRostyslav Aug 19, 2025
6ffae5c
Merge branch 'main' into dev
wiktorlazarski Aug 20, 2025
7dcbfda
Merge pull request #143 from worldcoin/merge_main_to_dev
wiktorlazarski Aug 20, 2025
ffb7396
Preserve ndarray in safe serialization; bump IRIS to 1.9.2 (#145)
ShevchenkoRostyslav Sep 1, 2025
48e7af6
Yichen1 aic 6094 pr for new interpolation fusion method (#144)
ycbiometrics Sep 1, 2025
bfac4e1
Merge branch 'dev' into fake_main
ShevchenkoRostyslav Sep 1, 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
2 changes: 1 addition & 1 deletion docs/source/_code_subpages/iris.pipelines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ iris.pipelines.iris\_pipeline module
iris.pipelines.multiframe\_aggregation\_pipeline module
-------------------------------------------------------

.. automodule:: iris.pipelines.templates_aggregation_pipeline
.. automodule:: iris.pipelines.multiframe_aggregation_pipeline
:members:
:undoc-members:
:show-inheritance:
Expand Down
2 changes: 1 addition & 1 deletion src/iris/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.9.1"
__version__ = "1.9.2"
122 changes: 103 additions & 19 deletions src/iris/nodes/geometry_estimation/fusion_extrapolation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import List

import numpy as np

from iris.callbacks.callback_interface import Callback
from iris.io.class_configs import Algorithm
from iris.io.dataclasses import EyeCenters, GeometryPolygons
Expand All @@ -9,11 +11,18 @@


class FusionExtrapolation(Algorithm):
"""Algorithm implements fusion extrapolation that consist of two concreate extrapolation algoriths.
1) circle extrapolation algorithm - linear extrapolation algorithm
2) ellipse extrapolation algorithm - least square ellipse fit with iris polygon refinement.
"""Fuse two extrapolation strategies and pick the result based on shape statistics.

1) Circle-like extrapolation: LinearExtrapolation (linear extrapolation algorithm)
2) Ellipse-like extrapolation: LSQEllipseFitWithRefinement (least square ellipse fit with iris polygon refinement)

By default, the linear extrapolation is used. If the (relative) spread of radii
exceeds a threshold and the ellipse-based result looks sufficiently "regular"
(based on squared radii ratios), we prefer the ellipse fit.

By default the linear extrapolation algorithm is used but if standard deviation of radiuses is greater than given threshold then least square ellipse fit algorithm is applied because eye is ver likely to be more elliptical then circular.
Notes:
- "Relative std" means std/mean with an epsilon guard.
- We compare regularity via the relative std of iris/pupil squared-radius ratios under three centering choices (circle/circle, ellipse/ellipse, ellipse/circle).
"""

class Parameters(Algorithm.Parameters):
Expand All @@ -22,33 +31,73 @@ class Parameters(Algorithm.Parameters):
circle_extrapolation: Algorithm
ellipse_fit: Algorithm
algorithm_switch_std_threshold: float
algorithm_switch_std_conditioned_multiplier: float

__parameters_type__ = Parameters

def __init__(
self,
circle_extrapolation: Algorithm = LinearExtrapolation(dphi=360 / 512),
ellipse_fit: Algorithm = LSQEllipseFitWithRefinement(dphi=360 / 512),
algorithm_switch_std_threshold: float = 3.5,
algorithm_switch_std_threshold: float = 0.014,
algorithm_switch_std_conditioned_multiplier: float = 2.0,
callbacks: List[Callback] = [],
) -> None:
"""Assign parameters.

Args:
circle_extrapolation (Algorithm, optional): More circular shape estimation algorithm. Defaults to LinearExtrapolation(dphi=360 / 512, degrees / width of normalized image).
ellipse_fit (Algorithm, optional): More elliptical shape estimation algorithm. Defaults to LSQEllipseFitWithRefinement(dphi=360 / 512, degrees / width of normalized image).
algorithm_switch_std_threshold (float, optional): Algorithm switch threshold. Defaults to 3.5.
algorithm_switch_std_threshold (float, optional): Threshold on (rel. std of iris + rel. std of pupil) beyond which we consider switching to ellipse.. Defaults to 0.014.
algorithm_switch_std_conditioned_multiplier (float, optional): How strongly iris spread penalizes circle regularity in the switch condition. Default: 2.0.
callbacks (List[Callback], optional): _description_. Defaults to [].
"""
super().__init__(
ellipse_fit=ellipse_fit,
circle_extrapolation=circle_extrapolation,
algorithm_switch_std_threshold=algorithm_switch_std_threshold,
algorithm_switch_std_conditioned_multiplier=algorithm_switch_std_conditioned_multiplier,
callbacks=callbacks,
)

@staticmethod
def _relative_std(x: np.ndarray, eps: float = 1e-12) -> float:
"""
Relative std = std / max(mean, eps).

Args:
x: 1D array-like.
eps: Small guard to avoid division by zero.

Returns:
float: relative std.
"""
x = np.asarray(x)
m = float(np.mean(x))
s = float(np.std(x))
return s / max(abs(m), eps)

@staticmethod
def _squared_relative_radii(
iris_centered: np.ndarray, pupil_centered: np.ndarray, eps: float = 1e-12
) -> np.ndarray:
"""
Ratio of iris over pupil squared radii, element wise.

Args:
iris_centered: (N, 2) centered iris points.
pupil_centered: (N, 2) centered pupil points.
eps: Small guard to avoid division by zero.

Returns:
(N,) array: iris_sq / pupil_sq
"""
iris_sq = np.sum(iris_centered**2, axis=1)
pupil_sq = np.sum(pupil_centered**2, axis=1)
return np.divide(iris_sq, np.maximum(pupil_sq, eps))

def run(self, input_polygons: GeometryPolygons, eye_center: EyeCenters) -> GeometryPolygons:
"""Perform extrapolation algorithm.
"""Perform extrapolation algorithm and select the most plausible result.

Args:
input_polygons (GeometryPolygons): Smoothed polygons.
Expand All @@ -57,18 +106,53 @@ def run(self, input_polygons: GeometryPolygons, eye_center: EyeCenters) -> Geome
Returns:
GeometryPolygons: Extrapolated polygons
"""
xs, ys = input_polygons.iris_array[:, 0], input_polygons.iris_array[:, 1]
rhos, _ = cartesian2polar(xs, ys, eye_center.iris_x, eye_center.iris_y)
xs_iris, ys_iris = input_polygons.iris_array[:, 0], input_polygons.iris_array[:, 1]
rhos_iris, _ = cartesian2polar(xs_iris, ys_iris, eye_center.iris_x, eye_center.iris_y)
xs_pupil, ys_pupil = input_polygons.pupil_array[:, 0], input_polygons.pupil_array[:, 1]
rhos_pupil, _ = cartesian2polar(xs_pupil, ys_pupil, eye_center.pupil_x, eye_center.pupil_y)
circle_poly = self.params.circle_extrapolation(input_polygons, eye_center)
ellipse_poly = self.params.ellipse_fit(input_polygons)

circle_iris = circle_poly.iris_array
circle_pupil = circle_poly.pupil_array
ellipse_iris = ellipse_poly.iris_array
ellipse_pupil = ellipse_poly.pupil_array

circle_center = np.mean(circle_pupil, axis=0)
ellipse_center = np.mean(ellipse_pupil, axis=0)

circle_iris_centered = circle_iris - circle_center
circle_pupil_centered = circle_pupil - circle_center
ellipse_iris_centered = ellipse_iris - ellipse_center
ellipse_pupil_centered = ellipse_pupil - ellipse_center
ec_iris_centered = ellipse_iris - circle_center

cc = self._squared_relative_radii(circle_iris_centered, circle_pupil_centered)
ee = self._squared_relative_radii(ellipse_iris_centered, ellipse_pupil_centered)
ec = self._squared_relative_radii(ec_iris_centered, circle_pupil_centered)
cc_reg = self._relative_std(cc)
ee_reg = self._relative_std(ee)
ec_reg = self._relative_std(ec)

radius_std_iris = self._relative_std(rhos_iris)
radius_std_pupil = self._relative_std(rhos_pupil)
min_reg = min(ee_reg, ec_reg)

new_poly = self.params.circle_extrapolation(input_polygons, eye_center)
spread_ok = (radius_std_iris + radius_std_pupil) >= self.params.algorithm_switch_std_threshold
reg_ok = min_reg <= (cc_reg + radius_std_iris * self.params.algorithm_switch_std_conditioned_multiplier)

radius_std = rhos.std()
if radius_std > self.params.algorithm_switch_std_threshold:
ellipse_poly = self.params.ellipse_fit(input_polygons)
new_poly = GeometryPolygons(
pupil_array=new_poly.pupil_array,
iris_array=ellipse_poly.iris_array,
eyeball_array=input_polygons.eyeball_array,
)
if spread_ok and reg_ok:
if ee_reg <= ec_reg:
return GeometryPolygons(
pupil_array=ellipse_poly.pupil_array,
iris_array=ellipse_poly.iris_array,
eyeball_array=input_polygons.eyeball_array,
)
else:
return GeometryPolygons(
pupil_array=circle_poly.pupil_array,
iris_array=ellipse_poly.iris_array,
eyeball_array=input_polygons.eyeball_array,
)

return new_poly
return circle_poly
2 changes: 1 addition & 1 deletion src/iris/orchestration/output_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __safe_serialize(object: Optional[Any]) -> Optional[Any]:
elif isinstance(object, (list, tuple)):
return type(object)(__safe_serialize(sub_object) for sub_object in object)
elif isinstance(object, np.ndarray):
return object.tolist()
return object
elif isinstance(object, (str, int, float, bool)):
return object
else:
Expand Down
9 changes: 5 additions & 4 deletions src/iris/pipelines/confs/multiframe_iris_pipeline.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
metadata:
pipeline_name: multiframe_iris_pipeline
iris_version: 1.9.1
iris_version: 1.9.2

# Configuration for individual image processing (IRISPipeline)
iris_pipeline:
metadata:
pipeline_name: iris_pipeline
iris_version: 1.9.1
iris_version: 1.9.2

pipeline:
- name: segmentation
Expand Down Expand Up @@ -120,7 +120,8 @@ iris_pipeline:
class_name: iris.LSQEllipseFitWithRefinement
params:
dphi: 0.703125
algorithm_switch_std_threshold: 3.5
algorithm_switch_std_threshold: 0.014
algorithm_switch_std_conditioned_multiplier: 2.0
inputs:
- name: input_polygons
source_node: smoothing
Expand Down Expand Up @@ -313,7 +314,7 @@ iris_pipeline:
# Configuration for template aggregation (TemplatesAggregationPipeline)
templates_aggregation_pipeline:
metadata:
iris_version: 1.9.1
iris_version: 1.9.2
pipeline_name: templates_aggregation

pipeline:
Expand Down
5 changes: 3 additions & 2 deletions src/iris/pipelines/confs/pipeline.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
metadata:
pipeline_name: iris_pipeline
iris_version: 1.9.1
iris_version: 1.9.2

pipeline:
- name: segmentation
Expand Down Expand Up @@ -114,7 +114,8 @@ pipeline:
class_name: iris.LSQEllipseFitWithRefinement
params:
dphi: 0.703125
algorithm_switch_std_threshold: 3.5
algorithm_switch_std_threshold: 0.014
algorithm_switch_std_conditioned_multiplier: 2.0
inputs:
- name: input_polygons
source_node: smoothing
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
metadata:
pipeline_name: iris_pipeline
iris_version: 1.9.1
iris_version: 1.9.2

pipeline:
- name: segmentation
Expand Down Expand Up @@ -114,7 +114,8 @@ pipeline:
class_name: iris.LSQEllipseFitWithRefinement
params:
dphi: 0.703125
algorithm_switch_std_threshold: 3.5
algorithm_switch_std_threshold: 0.014
algorithm_switch_std_conditioned_multiplier: 2.0
inputs:
- name: input_polygons
source_node: smoothing
Expand Down Expand Up @@ -305,7 +306,7 @@ pipeline:

templates_aggregation:
metadata:
iris_version: 1.9.1
iris_version: 1.9.2
pipeline_name: templates_aggregation

pipeline:
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 2 additions & 6 deletions tests/unit_tests/orchestration/test_output_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,19 +288,15 @@ def test_immutable_model_serialization(self):
eye_centers = EyeCenters(pupil_x=1.0, pupil_y=2.0, iris_x=3.0, iris_y=4.0)
assert SAFE_SERIALIZE(eye_centers) == eye_centers.serialize()

def test_numpy_array_to_list(self):
arr = np.array([[1, 2], [3, 4]])
assert SAFE_SERIALIZE(arr) == arr.tolist()

def test_list_and_tuple_recursion(self):
offgaze = Offgaze(score=0.25)
arr = np.array([1, 2, 3])

data_list = [1, "a", offgaze, arr]
assert SAFE_SERIALIZE(data_list) == [1, "a", offgaze.serialize(), arr.tolist()]
assert SAFE_SERIALIZE(data_list) == [1, "a", offgaze.serialize(), arr]

data_tuple = (True, 3.14, offgaze, arr)
assert SAFE_SERIALIZE(data_tuple) == (True, 3.14, offgaze.serialize(), arr.tolist())
assert SAFE_SERIALIZE(data_tuple) == (True, 3.14, offgaze.serialize(), arr)

def test_primitives_passthrough(self):
assert SAFE_SERIALIZE("hello") == "hello"
Expand Down