Skip to content

Commit f0a39cd

Browse files
ShevchenkoRostyslavycbiometricswiktorlazarskiycbiometrics
authored
Release 1.9.2 (#147)
* tets workflow * upd * add prod workflow * correct the repo name * upd name * add a workflow to create docs * address PR comments * debugging * add requirements * upd deploy docs * docs branch * fix bug * cleaning * new way * fake upd of docds * upd * upd * rollback * upd * upd * revert * worktree * another approach * upd * keep some files * final version * swap the order * upd * upd * upd * remove .buildinfo * remove .buildinfo * cleaning * Iris multiview/aic 5901: e2e Multiframe Iris Pipeline (#123) * refactor multiframe iris pipeline * refactor and fix tests * fix test leakage from patches * upd e2e tests * fix broken tests * rename MultiframeAggregationPipeline -> TemplatesAggregation * Add user warning * bump the iris version * 1.8.3 -> 1.9.0 (#137) Co-authored-by: ycbiometrics <107422087+ycbiometrics@users.noreply.github.com> * Make sure that NodeWritterResult callback is always first in callbacks list of every node. * Hotfix/output builders type (#139) * 1.8.3 -> 1.9.0 * bugfix serialization data type * add speciofic e2e test for multiframe * revert the doc file from 1.9.0 releqase * Hotfix/output builders type (#141) * 1.8.3 -> 1.9.0 * release v1.9.0 (#136) * tets workflow * upd * add prod workflow * correct the repo name * upd name * add a workflow to create docs * address PR comments * debugging * add requirements * upd deploy docs * docs branch * fix bug * cleaning * new way * fake upd of docds * upd * upd * rollback * upd * upd * revert * worktree * another approach * upd * keep some files * final version * swap the order * upd * upd * upd * remove .buildinfo * remove .buildinfo * cleaning * Iris multiview/aic 5901: e2e Multiframe Iris Pipeline (#123) * refactor multiframe iris pipeline * refactor and fix tests * fix test leakage from patches * upd e2e tests * fix broken tests * rename MultiframeAggregationPipeline -> TemplatesAggregation * Add user warning * bump the iris version * 1.8.3 -> 1.9.0 (#137) Co-authored-by: ycbiometrics <107422087+ycbiometrics@users.noreply.github.com> --------- Co-authored-by: ycbiometrics <107422087+ycbiometrics@users.noreply.github.com> * bugfix serialization data type * add speciofic e2e test for multiframe * revert the doc file from 1.9.0 releqase * update iris version * upd --------- Co-authored-by: ycbiometrics <107422087+ycbiometrics@users.noreply.github.com> * Preserve ndarray in safe serialization; bump IRIS to 1.9.2 (#145) * fix: not to cast ndarray to list * fix tests * bump the version * rollback docs * Yichen1 aic 6094 pr for new interpolation fusion method (#144) * improve the fusion methold on boundary extrapolation * update more config files * update unit tests --------- Co-authored-by: ycbiometrics <yi.chen@ip-10-54-144-12.eu-central-1.compute.internal> --------- Co-authored-by: ycbiometrics <107422087+ycbiometrics@users.noreply.github.com> Co-authored-by: Wiktor Łazarski <wjlazarski@gmail.com> Co-authored-by: ycbiometrics <yi.chen@ip-10-54-144-12.eu-central-1.compute.internal>
1 parent d45283b commit f0a39cd

11 files changed

Lines changed: 120 additions & 37 deletions

File tree

docs/source/_code_subpages/iris.pipelines.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ iris.pipelines.iris\_pipeline module
2323
iris.pipelines.multiframe\_aggregation\_pipeline module
2424
-------------------------------------------------------
2525

26-
.. automodule:: iris.pipelines.templates_aggregation_pipeline
26+
.. automodule:: iris.pipelines.multiframe_aggregation_pipeline
2727
:members:
2828
:undoc-members:
2929
:show-inheritance:

src/iris/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.9.1"
1+
__version__ = "1.9.2"
Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import List
22

3+
import numpy as np
4+
35
from iris.callbacks.callback_interface import Callback
46
from iris.io.class_configs import Algorithm
57
from iris.io.dataclasses import EyeCenters, GeometryPolygons
@@ -9,11 +11,18 @@
911

1012

1113
class FusionExtrapolation(Algorithm):
12-
"""Algorithm implements fusion extrapolation that consist of two concreate extrapolation algoriths.
13-
1) circle extrapolation algorithm - linear extrapolation algorithm
14-
2) ellipse extrapolation algorithm - least square ellipse fit with iris polygon refinement.
14+
"""Fuse two extrapolation strategies and pick the result based on shape statistics.
15+
16+
1) Circle-like extrapolation: LinearExtrapolation (linear extrapolation algorithm)
17+
2) Ellipse-like extrapolation: LSQEllipseFitWithRefinement (least square ellipse fit with iris polygon refinement)
18+
19+
By default, the linear extrapolation is used. If the (relative) spread of radii
20+
exceeds a threshold and the ellipse-based result looks sufficiently "regular"
21+
(based on squared radii ratios), we prefer the ellipse fit.
1522
16-
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.
23+
Notes:
24+
- "Relative std" means std/mean with an epsilon guard.
25+
- We compare regularity via the relative std of iris/pupil squared-radius ratios under three centering choices (circle/circle, ellipse/ellipse, ellipse/circle).
1726
"""
1827

1928
class Parameters(Algorithm.Parameters):
@@ -22,33 +31,73 @@ class Parameters(Algorithm.Parameters):
2231
circle_extrapolation: Algorithm
2332
ellipse_fit: Algorithm
2433
algorithm_switch_std_threshold: float
34+
algorithm_switch_std_conditioned_multiplier: float
2535

2636
__parameters_type__ = Parameters
2737

2838
def __init__(
2939
self,
3040
circle_extrapolation: Algorithm = LinearExtrapolation(dphi=360 / 512),
3141
ellipse_fit: Algorithm = LSQEllipseFitWithRefinement(dphi=360 / 512),
32-
algorithm_switch_std_threshold: float = 3.5,
42+
algorithm_switch_std_threshold: float = 0.014,
43+
algorithm_switch_std_conditioned_multiplier: float = 2.0,
3344
callbacks: List[Callback] = [],
3445
) -> None:
3546
"""Assign parameters.
3647
3748
Args:
3849
circle_extrapolation (Algorithm, optional): More circular shape estimation algorithm. Defaults to LinearExtrapolation(dphi=360 / 512, degrees / width of normalized image).
3950
ellipse_fit (Algorithm, optional): More elliptical shape estimation algorithm. Defaults to LSQEllipseFitWithRefinement(dphi=360 / 512, degrees / width of normalized image).
40-
algorithm_switch_std_threshold (float, optional): Algorithm switch threshold. Defaults to 3.5.
51+
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.
52+
algorithm_switch_std_conditioned_multiplier (float, optional): How strongly iris spread penalizes circle regularity in the switch condition. Default: 2.0.
4153
callbacks (List[Callback], optional): _description_. Defaults to [].
4254
"""
4355
super().__init__(
4456
ellipse_fit=ellipse_fit,
4557
circle_extrapolation=circle_extrapolation,
4658
algorithm_switch_std_threshold=algorithm_switch_std_threshold,
59+
algorithm_switch_std_conditioned_multiplier=algorithm_switch_std_conditioned_multiplier,
4760
callbacks=callbacks,
4861
)
4962

63+
@staticmethod
64+
def _relative_std(x: np.ndarray, eps: float = 1e-12) -> float:
65+
"""
66+
Relative std = std / max(mean, eps).
67+
68+
Args:
69+
x: 1D array-like.
70+
eps: Small guard to avoid division by zero.
71+
72+
Returns:
73+
float: relative std.
74+
"""
75+
x = np.asarray(x)
76+
m = float(np.mean(x))
77+
s = float(np.std(x))
78+
return s / max(abs(m), eps)
79+
80+
@staticmethod
81+
def _squared_relative_radii(
82+
iris_centered: np.ndarray, pupil_centered: np.ndarray, eps: float = 1e-12
83+
) -> np.ndarray:
84+
"""
85+
Ratio of iris over pupil squared radii, element wise.
86+
87+
Args:
88+
iris_centered: (N, 2) centered iris points.
89+
pupil_centered: (N, 2) centered pupil points.
90+
eps: Small guard to avoid division by zero.
91+
92+
Returns:
93+
(N,) array: iris_sq / pupil_sq
94+
"""
95+
iris_sq = np.sum(iris_centered**2, axis=1)
96+
pupil_sq = np.sum(pupil_centered**2, axis=1)
97+
return np.divide(iris_sq, np.maximum(pupil_sq, eps))
98+
5099
def run(self, input_polygons: GeometryPolygons, eye_center: EyeCenters) -> GeometryPolygons:
51-
"""Perform extrapolation algorithm.
100+
"""Perform extrapolation algorithm and select the most plausible result.
52101
53102
Args:
54103
input_polygons (GeometryPolygons): Smoothed polygons.
@@ -57,18 +106,53 @@ def run(self, input_polygons: GeometryPolygons, eye_center: EyeCenters) -> Geome
57106
Returns:
58107
GeometryPolygons: Extrapolated polygons
59108
"""
60-
xs, ys = input_polygons.iris_array[:, 0], input_polygons.iris_array[:, 1]
61-
rhos, _ = cartesian2polar(xs, ys, eye_center.iris_x, eye_center.iris_y)
109+
xs_iris, ys_iris = input_polygons.iris_array[:, 0], input_polygons.iris_array[:, 1]
110+
rhos_iris, _ = cartesian2polar(xs_iris, ys_iris, eye_center.iris_x, eye_center.iris_y)
111+
xs_pupil, ys_pupil = input_polygons.pupil_array[:, 0], input_polygons.pupil_array[:, 1]
112+
rhos_pupil, _ = cartesian2polar(xs_pupil, ys_pupil, eye_center.pupil_x, eye_center.pupil_y)
113+
circle_poly = self.params.circle_extrapolation(input_polygons, eye_center)
114+
ellipse_poly = self.params.ellipse_fit(input_polygons)
115+
116+
circle_iris = circle_poly.iris_array
117+
circle_pupil = circle_poly.pupil_array
118+
ellipse_iris = ellipse_poly.iris_array
119+
ellipse_pupil = ellipse_poly.pupil_array
120+
121+
circle_center = np.mean(circle_pupil, axis=0)
122+
ellipse_center = np.mean(ellipse_pupil, axis=0)
123+
124+
circle_iris_centered = circle_iris - circle_center
125+
circle_pupil_centered = circle_pupil - circle_center
126+
ellipse_iris_centered = ellipse_iris - ellipse_center
127+
ellipse_pupil_centered = ellipse_pupil - ellipse_center
128+
ec_iris_centered = ellipse_iris - circle_center
129+
130+
cc = self._squared_relative_radii(circle_iris_centered, circle_pupil_centered)
131+
ee = self._squared_relative_radii(ellipse_iris_centered, ellipse_pupil_centered)
132+
ec = self._squared_relative_radii(ec_iris_centered, circle_pupil_centered)
133+
cc_reg = self._relative_std(cc)
134+
ee_reg = self._relative_std(ee)
135+
ec_reg = self._relative_std(ec)
136+
137+
radius_std_iris = self._relative_std(rhos_iris)
138+
radius_std_pupil = self._relative_std(rhos_pupil)
139+
min_reg = min(ee_reg, ec_reg)
62140

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

65-
radius_std = rhos.std()
66-
if radius_std > self.params.algorithm_switch_std_threshold:
67-
ellipse_poly = self.params.ellipse_fit(input_polygons)
68-
new_poly = GeometryPolygons(
69-
pupil_array=new_poly.pupil_array,
70-
iris_array=ellipse_poly.iris_array,
71-
eyeball_array=input_polygons.eyeball_array,
72-
)
144+
if spread_ok and reg_ok:
145+
if ee_reg <= ec_reg:
146+
return GeometryPolygons(
147+
pupil_array=ellipse_poly.pupil_array,
148+
iris_array=ellipse_poly.iris_array,
149+
eyeball_array=input_polygons.eyeball_array,
150+
)
151+
else:
152+
return GeometryPolygons(
153+
pupil_array=circle_poly.pupil_array,
154+
iris_array=ellipse_poly.iris_array,
155+
eyeball_array=input_polygons.eyeball_array,
156+
)
73157

74-
return new_poly
158+
return circle_poly

src/iris/orchestration/output_builders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __safe_serialize(object: Optional[Any]) -> Optional[Any]:
6969
elif isinstance(object, (list, tuple)):
7070
return type(object)(__safe_serialize(sub_object) for sub_object in object)
7171
elif isinstance(object, np.ndarray):
72-
return object.tolist()
72+
return object
7373
elif isinstance(object, (str, int, float, bool)):
7474
return object
7575
else:

src/iris/pipelines/confs/multiframe_iris_pipeline.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
metadata:
22
pipeline_name: multiframe_iris_pipeline
3-
iris_version: 1.9.1
3+
iris_version: 1.9.2
44

55
# Configuration for individual image processing (IRISPipeline)
66
iris_pipeline:
77
metadata:
88
pipeline_name: iris_pipeline
9-
iris_version: 1.9.1
9+
iris_version: 1.9.2
1010

1111
pipeline:
1212
- name: segmentation
@@ -120,7 +120,8 @@ iris_pipeline:
120120
class_name: iris.LSQEllipseFitWithRefinement
121121
params:
122122
dphi: 0.703125
123-
algorithm_switch_std_threshold: 3.5
123+
algorithm_switch_std_threshold: 0.014
124+
algorithm_switch_std_conditioned_multiplier: 2.0
124125
inputs:
125126
- name: input_polygons
126127
source_node: smoothing
@@ -313,7 +314,7 @@ iris_pipeline:
313314
# Configuration for template aggregation (TemplatesAggregationPipeline)
314315
templates_aggregation_pipeline:
315316
metadata:
316-
iris_version: 1.9.1
317+
iris_version: 1.9.2
317318
pipeline_name: templates_aggregation
318319

319320
pipeline:

src/iris/pipelines/confs/pipeline.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
metadata:
22
pipeline_name: iris_pipeline
3-
iris_version: 1.9.1
3+
iris_version: 1.9.2
44

55
pipeline:
66
- name: segmentation
@@ -114,7 +114,8 @@ pipeline:
114114
class_name: iris.LSQEllipseFitWithRefinement
115115
params:
116116
dphi: 0.703125
117-
algorithm_switch_std_threshold: 3.5
117+
algorithm_switch_std_threshold: 0.014
118+
algorithm_switch_std_conditioned_multiplier: 2.0
118119
inputs:
119120
- name: input_polygons
120121
source_node: smoothing

src/iris/pipelines/confs/templates_aggregation_pipeline.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
metadata:
22
pipeline_name: iris_pipeline
3-
iris_version: 1.9.1
3+
iris_version: 1.9.2
44

55
pipeline:
66
- name: segmentation
@@ -114,7 +114,8 @@ pipeline:
114114
class_name: iris.LSQEllipseFitWithRefinement
115115
params:
116116
dphi: 0.703125
117-
algorithm_switch_std_threshold: 3.5
117+
algorithm_switch_std_threshold: 0.014
118+
algorithm_switch_std_conditioned_multiplier: 2.0
118119
inputs:
119120
- name: input_polygons
120121
source_node: smoothing
@@ -305,7 +306,7 @@ pipeline:
305306

306307
templates_aggregation:
307308
metadata:
308-
iris_version: 1.9.1
309+
iris_version: 1.9.2
309310
pipeline_name: templates_aggregation
310311

311312
pipeline:
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)