Configuring custom pipeline tutorial¶
This subpage will walk you through you the steps you have to take to configure your custom IRISPipeline
. From it you will learn how to:
Configure
IRISPipeline
algorithms parameters.Configure
IRISPipeline
graph.Implement your own node with
Algorithm
class and introduce them intoIRISPipeline
graph.
1. Configure IRISPipeline
algorithms parameters.¶
To create the IRISPipeline
object with modified Algorithm
parameters, we have to understand a few things about IRISPipeline
orchestration system.
When the IRISPipeline
pipeline is created with default parameters, it’s graph is parsed from a default YAML file that is available in /src/iris/pipelines/confs/pipeline.yaml
YAML file. The content of that file presents as follow.
metadata:
pipeline_name: iris_pipeline
iris_version: 1.1.1
The top YAML file contains IRISPipeline
metadata, used to both describe IRISPipeline
and specify package parameters that are later used to verify compatibility between iris
package version/release and later, specified in the pipeline
YAML file section, pipeline’s graph.
pipeline:
- name: segmentation
algorithm:
class_name: iris.MultilabelSegmentation.create_from_hugging_face
params: {}
inputs:
- name: image
source_node: input
callbacks:
- name: segmentation_binarization
algorithm:
class_name: iris.MultilabelSegmentationBinarization
params: {}
inputs:
- name: segmentation_map
source_node: segmentation
callbacks:
...
The pipeline
subsection contains a list of IRISPipeline
nodes. The node definition has to contain following keys:
name
- that’s node metadata information about node name. It’s used later to define connections with other defined nodes. Also, it’s worth to notice that thename
key is later used byPipelineCallTraceStorage
to store and return different intermediate results.algorithm
- that’s a key that contains a definition of a Python object that implements an algorithm we want to use in our pipeline.algorithms.class_name
- a Python object class name that implementsiris.Algorithm
interface (more information aboutAlgorithm
class will be provided in section 3 of this tutorial). Please note, that defined here Python object must be importable by Python interpreter. That means thatAlgorithm
implementation doesn’t have to be implemented withiniris
package. User may implement or import it from any external library. The only constraint is thatAlgorithm
interface must be satisfied to make everything compatible.algorithms.params
- that key defined a dictionary that contains all__init__
parameters of a given node -Algorithm
object. List of parameters of nodes available in theiris
package with their descriptions can be found in project documentation.inputs
- that key defined a list of inputs to node’srun
method - connections between node within pipeline graph. A single input record has to contain following keys:["name", "source_node"]
. Optionally, aninputs
record can contain anindex
key. It’s used whenever input node returns a tuple/list of objects and user wants to extract a certain output to be provided torun
method of currently defined node. An example of a node definition that utilizedindex
can look like follow:
- name: vectorization
algorithm:
class_name: iris.ContouringAlgorithm
params: {}
inputs:
- name: geometry_mask
source_node: segmentation_binarization
index: 0
callbacks:
inputs.name
- theAlgorithm
run
method argument name that is meant to be filled with the output from thesource_name
.inputs.source_name
- a name of node that outputs input to currently defined node.callbacks
- a key that defines a list of possibleiris.Callback
object of a node. That key requires from anAlgorithm
object to allow callback plug in. User can allow that behaviour when specifyingcallbacks
argument of the__init__
method of particularAlgorithm
.
NOTE: Nodes has to be defined consecutively with the order they appear within pipeline. That means that specifying source_name
to the node which definition appears later within YAML file will cause exception being raised when instantiating pipeline.
A default pipeline configuration specified within YAML file can be found in /src/iris/pipelines/confs/pipeline.yaml.
Other then YAML file, user may defined and provide to __init__
method a Python dictionary with similar structure as described above YAML file.
Below examples shows how to modify iris.MultilabelSegmentationBinarization
algorithm thresholds to use other than specified by default 0.5
. The iris.MultilabelSegmentationBinarization
__init__
method is defined as follow:
class MultilabelSegmentationBinarization(Algorithm):
def __init__(
self,
eyeball_threshold: float = 0.5,
iris_threshold: float = 0.5,
pupil_threshold: float = 0.5,
eyelashes_threshold: float = 0.5,
) -> None:
...
...
First let’s intantiate IRISPipeline
with default configuration and see iris.MultilabelSegmentationBinarization
threshold values.
default_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
"algorithm": {"class_name": "iris.MultilabelSegmentation.create_from_hugging_face", "params": {}},
"inputs": [{"name": "image", "source_node": "input"}],
"callbacks": None,
},
############################### A NODE, WHICH PARAMETERS WE WANT TO MODIFY ################################
{
"name": "segmentation_binarization",
"algorithm": {"class_name": "iris.MultilabelSegmentationBinarization", "params": {}},
"inputs": [{"name": "segmentation_map", "source_node": "segmentation"}],
"callbacks": None,
},
############################################################################################################
{
"name": "vectorization",
"algorithm": {"class_name": "iris.ContouringAlgorithm", "params": {}},
"inputs": [{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 0}],
"callbacks": None,
},
{
"name": "specular_reflection_detection",
"algorithm": {"class_name": "iris.SpecularReflectionDetection", "params": {}},
"inputs": [{"name": "ir_image", "source_node": "input"}],
"callbacks": None,
},
{
"name": "interpolation",
"algorithm": {"class_name": "iris.ContourInterpolation", "params": {}},
"inputs": [{"name": "polygons", "source_node": "vectorization"}],
"callbacks": None,
},
{
"name": "distance_filter",
"algorithm": {"class_name": "iris.ContourPointNoiseEyeballDistanceFilter", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "interpolation"},
{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 1},
],
"callbacks": None,
},
{
"name": "eye_orientation",
"algorithm": {"class_name": "iris.MomentOfArea", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "eye_center_estimation",
"algorithm": {"class_name": "iris.BisectorsMethod", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "smoothing",
"algorithm": {"class_name": "iris.Smoothing", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "distance_filter"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "geometry_estimation",
"algorithm": {
"class_name": "iris.FusionExtrapolation",
"params": {
"circle_extrapolation": {"class_name": "iris.LinearExtrapolation", "params": {"dphi": 0.703125}},
"ellipse_fit": {"class_name": "iris.LSQEllipseFitWithRefinement", "params": {"dphi": 0.703125}},
"algorithm_switch_std_threshold": 3.5,
},
},
"inputs": [
{"name": "input_polygons", "source_node": "smoothing"},
{"name": "eye_center", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "pupil_to_iris_property_estimation",
"algorithm": {"class_name": "iris.PupilIrisPropertyCalculator", "params": {}},
"inputs": [
{"name": "geometries", "source_node": "geometry_estimation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "offgaze_estimation",
"algorithm": {"class_name": "iris.EccentricityOffgazeEstimation", "params": {}},
"inputs": [{"name": "geometries", "source_node": "geometry_estimation"}],
"callbacks": None,
},
{
"name": "occlusion90_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 90.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "occlusion30_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 30.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "noise_masks_aggregation",
"algorithm": {"class_name": "iris.NoiseMaskUnion", "params": {}},
"inputs": [
{
"name": "elements",
"source_node": [
{"name": "segmentation_binarization", "index": 1},
{"name": "specular_reflection_detection"},
],
}
],
"callbacks": None,
},
{
"name": "normalization",
"algorithm": {"class_name": "iris.PerspectiveNormalization", "params": {}},
"inputs": [
{"name": "image", "source_node": "input"},
{"name": "noise_mask", "source_node": "noise_masks_aggregation"},
{"name": "extrapolated_contours", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
],
"callbacks": None,
},
{
"name": "filter_bank",
"algorithm": {
"class_name": "iris.ConvFilterBank",
"params": {
"filters": [
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [41, 21],
"sigma_phi": 7,
"sigma_rho": 6.13,
"theta_degrees": 90.0,
"lambda_phi": 28.0,
"dc_correction": True,
"to_fixpoints": True,
},
},
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [17, 21],
"sigma_phi": 2,
"sigma_rho": 5.86,
"theta_degrees": 90.0,
"lambda_phi": 8,
"dc_correction": True,
"to_fixpoints": True,
},
},
],
"probe_schemas": [
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
],
},
},
"inputs": [{"name": "normalization_output", "source_node": "normalization"}],
"callbacks": None,
},
{
"name": "encoder",
"algorithm": {"class_name": "iris.IrisEncoder", "params": {}},
"inputs": [{"name": "response", "source_node": "filter_bank"}],
"callbacks": None,
},
{
"name": "bounding_box_estimation",
"algorithm": {"class_name": "iris.IrisBBoxCalculator", "params": {}},
"inputs": [
{"name": "ir_image", "source_node": "input"},
{"name": "geometry_polygons", "source_node": "geometry_estimation"},
],
"callbacks": None,
},
],
}
Instantiate IRISPipeline
object.
iris_pipeline = iris.IRISPipeline(config=default_pipeline_conf)
Print iris.MultilabelSegmentationBinarization
threshold values.
def print_segmentation_binarization_thresholds():
binarization_node = [node for node_name, node in iris_pipeline.nodes.items() if node_name == "segmentation_binarization"]
assert len(binarization_node) == 1
binarization_node = binarization_node[0]
print(binarization_node.params)
print_segmentation_binarization_thresholds()
Output: eyeball_threshold=0.5 iris_threshold=0.5 pupil_threshold=0.5 eyelashes_threshold=0.5
As expected all threshold values are set to default 0.5
value. Now, let’s modify those values to be equal to 0.1
.
new_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
"algorithm": {"class_name": "iris.MultilabelSegmentation.create_from_hugging_face", "params": {}},
"inputs": [{"name": "image", "source_node": "input"}],
"callbacks": None,
},
############################### A NODE, WHICH PARAMETERS WE WANT TO MODIFY ################################
{
"name": "segmentation_binarization",
"algorithm": {"class_name": "iris.MultilabelSegmentationBinarization", "params": {
"eyeball_threshold": 0.1,
"iris_threshold": 0.1,
"pupil_threshold": 0.1,
"eyelashes_threshold": 0.1}},
"inputs": [{"name": "segmentation_map", "source_node": "segmentation"}],
"callbacks": None,
},
############################################################################################################
{
"name": "vectorization",
"algorithm": {"class_name": "iris.ContouringAlgorithm", "params": {}},
"inputs": [{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 0}],
"callbacks": None,
},
{
"name": "specular_reflection_detection",
"algorithm": {"class_name": "iris.SpecularReflectionDetection", "params": {}},
"inputs": [{"name": "ir_image", "source_node": "input"}],
"callbacks": None,
},
{
"name": "interpolation",
"algorithm": {"class_name": "iris.ContourInterpolation", "params": {}},
"inputs": [{"name": "polygons", "source_node": "vectorization"}],
"callbacks": None,
},
{
"name": "distance_filter",
"algorithm": {"class_name": "iris.ContourPointNoiseEyeballDistanceFilter", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "interpolation"},
{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 1},
],
"callbacks": None,
},
{
"name": "eye_orientation",
"algorithm": {"class_name": "iris.MomentOfArea", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "eye_center_estimation",
"algorithm": {"class_name": "iris.BisectorsMethod", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "smoothing",
"algorithm": {"class_name": "iris.Smoothing", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "distance_filter"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "geometry_estimation",
"algorithm": {
"class_name": "iris.FusionExtrapolation",
"params": {
"circle_extrapolation": {"class_name": "iris.LinearExtrapolation", "params": {"dphi": 0.703125}},
"ellipse_fit": {"class_name": "iris.LSQEllipseFitWithRefinement", "params": {"dphi": 0.703125}},
"algorithm_switch_std_threshold": 3.5,
},
},
"inputs": [
{"name": "input_polygons", "source_node": "smoothing"},
{"name": "eye_center", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "pupil_to_iris_property_estimation",
"algorithm": {"class_name": "iris.PupilIrisPropertyCalculator", "params": {}},
"inputs": [
{"name": "geometries", "source_node": "geometry_estimation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "offgaze_estimation",
"algorithm": {"class_name": "iris.EccentricityOffgazeEstimation", "params": {}},
"inputs": [{"name": "geometries", "source_node": "geometry_estimation"}],
"callbacks": None,
},
{
"name": "occlusion90_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 90.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "occlusion30_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 30.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "noise_masks_aggregation",
"algorithm": {"class_name": "iris.NoiseMaskUnion", "params": {}},
"inputs": [
{
"name": "elements",
"source_node": [
{"name": "segmentation_binarization", "index": 1},
{"name": "specular_reflection_detection"},
],
}
],
"callbacks": None,
},
{
"name": "normalization",
"algorithm": {"class_name": "iris.PerspectiveNormalization", "params": {}},
"inputs": [
{"name": "image", "source_node": "input"},
{"name": "noise_mask", "source_node": "noise_masks_aggregation"},
{"name": "extrapolated_contours", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
],
"callbacks": None,
},
{
"name": "filter_bank",
"algorithm": {
"class_name": "iris.ConvFilterBank",
"params": {
"filters": [
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [41, 21],
"sigma_phi": 7,
"sigma_rho": 6.13,
"theta_degrees": 90.0,
"lambda_phi": 28.0,
"dc_correction": True,
"to_fixpoints": True,
},
},
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [17, 21],
"sigma_phi": 2,
"sigma_rho": 5.86,
"theta_degrees": 90.0,
"lambda_phi": 8,
"dc_correction": True,
"to_fixpoints": True,
},
},
],
"probe_schemas": [
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
],
},
},
"inputs": [{"name": "normalization_output", "source_node": "normalization"}],
"callbacks": None,
},
{
"name": "encoder",
"algorithm": {"class_name": "iris.IrisEncoder", "params": {}},
"inputs": [{"name": "response", "source_node": "filter_bank"}],
"callbacks": None,
},
{
"name": "bounding_box_estimation",
"algorithm": {"class_name": "iris.IrisBBoxCalculator", "params": {}},
"inputs": [
{"name": "ir_image", "source_node": "input"},
{"name": "geometry_polygons", "source_node": "geometry_estimation"},
],
"callbacks": None,
},
],
}
Reinstantiate IRISPipeline
object.
iris_pipeline = iris.IRISPipeline(config=new_pipeline_conf)
Print iris.MultilabelSegmentationBinarization
threshold values.
print_segmentation_binarization_thresholds()
Output: eyeball_threshold=0.1 iris_threshold=0.1 pupil_threshold=0.1 eyelashes_threshold=0.1
Perfect! We’ve just learned how to modify IRISPipeline
algorithms parameters. Now, let’s have a look how to modify IRISPipeline
node connections.
2. Configure IRISPipeline
graph.¶
As described in previous section to define connection between nodes, we utilize inputs
key within our YAML file or dictionary. Similar to previous tutorial, let’s start with instantiating a default IRISPipeline
and then modify “artificially” for demonstration purposes connections between distance_filter
(iris.ContourPointNoiseEyeballDistanceFilter
), smoothing
(iris.Smoothing
) and geometry_estimation
(iris.FusionExtrapolation
) nodes.
By default, smoothing
node, responsible for refinement of vectorized iris and pupil points is taking as an input the output of distance_filter
nodes, which btw is also doing refinement of vectorized iris and pupil points but of course a different one. The output of smoothing
node is later passed to final geometry_estimation
node as an input. Within commented section below user can follow that connection. Now, in this example let’s imagine we want to bypass smoothing
node and perform geometry_estimation
based on the output of distance_filter
node while still keeping smoothing
node.
First let’s instantiate IRISPipeline
with default configuration and see nodes connected to geometry_estimation
node.
default_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
"algorithm": {"class_name": "iris.MultilabelSegmentation.create_from_hugging_face", "params": {}},
"inputs": [{"name": "image", "source_node": "input"}],
"callbacks": None,
},
{
"name": "segmentation_binarization",
"algorithm": {"class_name": "iris.MultilabelSegmentationBinarization", "params": {
"eyeball_threshold": 0.1,
"iris_threshold": 0.1,
"pupil_threshold": 0.1,
"eyelashes_threshold": 0.1}},
"inputs": [{"name": "segmentation_map", "source_node": "segmentation"}],
"callbacks": None,
},
{
"name": "vectorization",
"algorithm": {"class_name": "iris.ContouringAlgorithm", "params": {}},
"inputs": [{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 0}],
"callbacks": None,
},
{
"name": "specular_reflection_detection",
"algorithm": {"class_name": "iris.SpecularReflectionDetection", "params": {}},
"inputs": [{"name": "ir_image", "source_node": "input"}],
"callbacks": None,
},
{
"name": "interpolation",
"algorithm": {"class_name": "iris.ContourInterpolation", "params": {}},
"inputs": [{"name": "polygons", "source_node": "vectorization"}],
"callbacks": None,
},
############################### A NODE, WHICH PARAMETERS WE WANT TO MODIFY ################################
{
"name": "distance_filter",
"algorithm": {"class_name": "iris.ContourPointNoiseEyeballDistanceFilter", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "interpolation"},
{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 1},
],
"callbacks": None,
},
{
"name": "eye_orientation",
"algorithm": {"class_name": "iris.MomentOfArea", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "eye_center_estimation",
"algorithm": {"class_name": "iris.BisectorsMethod", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "smoothing",
"algorithm": {"class_name": "iris.Smoothing", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "distance_filter"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "geometry_estimation",
"algorithm": {
"class_name": "iris.FusionExtrapolation",
"params": {
"circle_extrapolation": {"class_name": "iris.LinearExtrapolation", "params": {"dphi": 0.703125}},
"ellipse_fit": {"class_name": "iris.LSQEllipseFitWithRefinement", "params": {"dphi": 0.703125}},
"algorithm_switch_std_threshold": 3.5,
},
},
"inputs": [
{"name": "input_polygons", "source_node": "smoothing"},
{"name": "eye_center", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
############################################################################################################
{
"name": "pupil_to_iris_property_estimation",
"algorithm": {"class_name": "iris.PupilIrisPropertyCalculator", "params": {}},
"inputs": [
{"name": "geometries", "source_node": "geometry_estimation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "offgaze_estimation",
"algorithm": {"class_name": "iris.EccentricityOffgazeEstimation", "params": {}},
"inputs": [{"name": "geometries", "source_node": "geometry_estimation"}],
"callbacks": None,
},
{
"name": "occlusion90_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 90.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "occlusion30_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 30.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "noise_masks_aggregation",
"algorithm": {"class_name": "iris.NoiseMaskUnion", "params": {}},
"inputs": [
{
"name": "elements",
"source_node": [
{"name": "segmentation_binarization", "index": 1},
{"name": "specular_reflection_detection"},
],
}
],
"callbacks": None,
},
{
"name": "normalization",
"algorithm": {"class_name": "iris.PerspectiveNormalization", "params": {}},
"inputs": [
{"name": "image", "source_node": "input"},
{"name": "noise_mask", "source_node": "noise_masks_aggregation"},
{"name": "extrapolated_contours", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
],
"callbacks": None,
},
{
"name": "filter_bank",
"algorithm": {
"class_name": "iris.ConvFilterBank",
"params": {
"filters": [
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [41, 21],
"sigma_phi": 7,
"sigma_rho": 6.13,
"theta_degrees": 90.0,
"lambda_phi": 28.0,
"dc_correction": True,
"to_fixpoints": True,
},
},
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [17, 21],
"sigma_phi": 2,
"sigma_rho": 5.86,
"theta_degrees": 90.0,
"lambda_phi": 8,
"dc_correction": True,
"to_fixpoints": True,
},
},
],
"probe_schemas": [
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
],
},
},
"inputs": [{"name": "normalization_output", "source_node": "normalization"}],
"callbacks": None,
},
{
"name": "encoder",
"algorithm": {"class_name": "iris.IrisEncoder", "params": {}},
"inputs": [{"name": "response", "source_node": "filter_bank"}],
"callbacks": None,
},
{
"name": "bounding_box_estimation",
"algorithm": {"class_name": "iris.IrisBBoxCalculator", "params": {}},
"inputs": [
{"name": "ir_image", "source_node": "input"},
{"name": "geometry_polygons", "source_node": "geometry_estimation"},
],
"callbacks": None,
},
],
}
Instantiate IRISPipeline
object.
iris_pipeline = iris.IRISPipeline(config=default_pipeline_conf)
Print geometry_estimation
input nodes name.
def print_geometry_estimation_inputs():
geometry_estimation_node = [node for node in iris_pipeline.params.pipeline if node.name == "geometry_estimation"]
assert len(geometry_estimation_node) == 1
geometry_estimation_node = geometry_estimation_node[0]
print(geometry_estimation_node.inputs)
print_geometry_estimation_inputs()
Output: [PipelineInput(name='input_polygons', index=None, source_node='smoothing'), PipelineInput(name='eye_center', index=None, source_node='eye_center_estimation')]
As expected, input_polygons
argument of the run
method is taken from the smoothing
output. Let’s modify it to described before behaviour - input_polygons
argument of the run
method is take from the distance_filter
output.
new_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
"algorithm": {"class_name": "iris.MultilabelSegmentation.create_from_hugging_face", "params": {}},
"inputs": [{"name": "image", "source_node": "input"}],
"callbacks": None,
},
{
"name": "segmentation_binarization",
"algorithm": {"class_name": "iris.MultilabelSegmentationBinarization", "params": {
"eyeball_threshold": 0.1,
"iris_threshold": 0.1,
"pupil_threshold": 0.1,
"eyelashes_threshold": 0.1}},
"inputs": [{"name": "segmentation_map", "source_node": "segmentation"}],
"callbacks": None,
},
{
"name": "vectorization",
"algorithm": {"class_name": "iris.ContouringAlgorithm", "params": {}},
"inputs": [{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 0}],
"callbacks": None,
},
{
"name": "specular_reflection_detection",
"algorithm": {"class_name": "iris.SpecularReflectionDetection", "params": {}},
"inputs": [{"name": "ir_image", "source_node": "input"}],
"callbacks": None,
},
{
"name": "interpolation",
"algorithm": {"class_name": "iris.ContourInterpolation", "params": {}},
"inputs": [{"name": "polygons", "source_node": "vectorization"}],
"callbacks": None,
},
############################### A NODE, WHICH PARAMETERS WE WANT TO MODIFY ################################
{
"name": "distance_filter",
"algorithm": {"class_name": "iris.ContourPointNoiseEyeballDistanceFilter", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "interpolation"},
{"name": "geometry_mask", "source_node": "segmentation_binarization", "index": 1},
],
"callbacks": None,
},
{
"name": "eye_orientation",
"algorithm": {"class_name": "iris.MomentOfArea", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "eye_center_estimation",
"algorithm": {"class_name": "iris.BisectorsMethod", "params": {}},
"inputs": [{"name": "geometries", "source_node": "distance_filter"}],
"callbacks": None,
},
{
"name": "smoothing",
"algorithm": {"class_name": "iris.Smoothing", "params": {}},
"inputs": [
{"name": "polygons", "source_node": "distance_filter"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "geometry_estimation",
"algorithm": {
"class_name": "iris.FusionExtrapolation",
"params": {
"circle_extrapolation": {"class_name": "iris.LinearExtrapolation", "params": {"dphi": 0.703125}},
"ellipse_fit": {"class_name": "iris.LSQEllipseFitWithRefinement", "params": {"dphi": 0.703125}},
"algorithm_switch_std_threshold": 3.5,
},
},
"inputs": [
{"name": "input_polygons", "source_node": "distance_filter"},
{"name": "eye_center", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
############################################################################################################
{
"name": "pupil_to_iris_property_estimation",
"algorithm": {"class_name": "iris.PupilIrisPropertyCalculator", "params": {}},
"inputs": [
{"name": "geometries", "source_node": "geometry_estimation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "offgaze_estimation",
"algorithm": {"class_name": "iris.EccentricityOffgazeEstimation", "params": {}},
"inputs": [{"name": "geometries", "source_node": "geometry_estimation"}],
"callbacks": None,
},
{
"name": "occlusion90_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 90.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "occlusion30_calculator",
"algorithm": {"class_name": "iris.OcclusionCalculator", "params": {"quantile_angle": 30.0}},
"inputs": [
{"name": "noise_mask", "source_node": "segmentation_binarization", "index": 1},
{"name": "extrapolated_polygons", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
{"name": "eye_centers", "source_node": "eye_center_estimation"},
],
"callbacks": None,
},
{
"name": "noise_masks_aggregation",
"algorithm": {"class_name": "iris.NoiseMaskUnion", "params": {}},
"inputs": [
{
"name": "elements",
"source_node": [
{"name": "segmentation_binarization", "index": 1},
{"name": "specular_reflection_detection"},
],
}
],
"callbacks": None,
},
{
"name": "normalization",
"algorithm": {"class_name": "iris.PerspectiveNormalization", "params": {}},
"inputs": [
{"name": "image", "source_node": "input"},
{"name": "noise_mask", "source_node": "noise_masks_aggregation"},
{"name": "extrapolated_contours", "source_node": "geometry_estimation"},
{"name": "eye_orientation", "source_node": "eye_orientation"},
],
"callbacks": None,
},
{
"name": "filter_bank",
"algorithm": {
"class_name": "iris.ConvFilterBank",
"params": {
"filters": [
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [41, 21],
"sigma_phi": 7,
"sigma_rho": 6.13,
"theta_degrees": 90.0,
"lambda_phi": 28.0,
"dc_correction": True,
"to_fixpoints": True,
},
},
{
"class_name": "iris.GaborFilter",
"params": {
"kernel_size": [17, 21],
"sigma_phi": 2,
"sigma_rho": 5.86,
"theta_degrees": 90.0,
"lambda_phi": 8,
"dc_correction": True,
"to_fixpoints": True,
},
},
],
"probe_schemas": [
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
{"class_name": "iris.RegularProbeSchema", "params": {"n_rows": 16, "n_cols": 256}},
],
},
},
"inputs": [{"name": "normalization_output", "source_node": "normalization"}],
"callbacks": None,
},
{
"name": "encoder",
"algorithm": {"class_name": "iris.IrisEncoder", "params": {}},
"inputs": [{"name": "response", "source_node": "filter_bank"}],
"callbacks": None,
},
{
"name": "bounding_box_estimation",
"algorithm": {"class_name": "iris.IrisBBoxCalculator", "params": {}},
"inputs": [
{"name": "ir_image", "source_node": "input"},
{"name": "geometry_polygons", "source_node": "geometry_estimation"},
],
"callbacks": None,
},
],
}
Reinstantiate IRISPipeline
object.
iris_pipeline = iris.IRISPipeline(config=new_pipeline_conf)
Print geometry_estimation
input nodes name.
print_geometry_estimation_inputs()
Output: [PipelineInput(name='input_polygons', index=None, source_node='distance_filter'), PipelineInput(name='eye_center', index=None, source_node='eye_center_estimation')]
Perfect! Now, we can see that geometry_estimation
will use the output of distance_filter
node as an input. Last but not least, before concluding this tutorial, we have to learn how to implement our own custom nodes that can be plugged to IRISPipeline
.
3. Implement your own node with Algorithm
class and introduce them into IRISPipeline
graph.¶
The Algorithm
class is an abstract class that is a base class for every node and IRISPipeline
in the iris
packages. It’s defined as follow:
class Algorithm(abc.ABC):
"""Base class of every node of the iris recognition pipeline."""
class Parameters(ImmutableModel):
"""Default parameters."""
pass
__parameters_type__ = Parameters
def __init__(self, **kwargs: Any) -> None:
"""Init function."""
self._callbacks: List[Callback] = []
if "callbacks" in kwargs.keys():
self._callbacks = deepcopy(kwargs["callbacks"])
del kwargs["callbacks"]
self.params = self.__parameters_type__(**kwargs)
def __call__(self, *args: Any, **kwargs: Any) -> Any:
"""Make an object a functor.
Returns:
Any: Object specified by an interface.
"""
return self.execute(*args, **kwargs)
def execute(self, *args: Any, **kwargs: Any) -> Any:
"""Execute method and wrapped with hooks if such are specified.
Returns:
Any: Object specified by an interface.
"""
for callback_func in self._callbacks:
callback_func.on_execute_start(*args, **kwargs)
result = self.run(*args, **kwargs)
for callback_func in self._callbacks:
callback_func.on_execute_end(result)
return result
def run(self, *args: Any, **kwargs: Any) -> Any:
"""Implement method design pattern. Not overwritten by subclass will raise an error.
Raises:
NotImplementedError: Raised if subclass doesn't implement `run` method.
Returns:
Any: Return value by concrete implementation of the `run` method.
"""
raise NotImplementedError(f"{self.__class__.__name__}.run method not implemented!")
There are 3 important things to note that have direct implications on how user have to implement custom Algorithm
:
The
run
method - If we implement our own customAlgorithm
we have to make sure thatrun
method is implemented. Other then that, already mentioned callbacks.The
__parameters_type__
variable - In our code base, we usepydantic
package to perform validation ofAlgorithm
__init__
parameters. To simplify and hide behind the screen those mechanisms, we introduced this variable.The
callbacks
special key that can be provided in the__init__
method. As already mentioned before, if we want to turn on in ourAlgorithm
callbacks mechanisms, we have to specify special -callbacks
- parameter in thatAlgorithm
__init__
method.
In this section, we won’t provide examples since there are plenty of them within the iris
package. Plus, we also want to encourage you to explore the iris
package by yourself. Therefore, for examples of concrete Algorithm
implementations, please check iris.nodes
submodule of the iris
package.
Thank you for making it to the end of this tutorial!