Skip to content

Commit

Permalink
Merge pull request #39 from worldcoin/tanguyjeanneau/open-iris-v1.1.1
Browse files Browse the repository at this point in the history
open-iris v1.1.1
  • Loading branch information
TanguyJeanneau authored Jul 8, 2024
2 parents 56e905d + 919359f commit 33036b2
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 59 deletions.
10 changes: 5 additions & 5 deletions colab/ConfiguringCustomPipeline.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
"```yaml\n",
"metadata:\n",
" pipeline_name: iris_pipeline\n",
" iris_version: 1.1.0\n",
" iris_version: 1.1.1\n",
"```\n",
"\n",
"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.\n",
Expand Down Expand Up @@ -205,7 +205,7 @@
"outputs": [],
"source": [
"default_pipeline_conf = {\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.0\"},\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.1\"},\n",
" \"pipeline\": [\n",
" {\n",
" \"name\": \"segmentation\",\n",
Expand Down Expand Up @@ -480,7 +480,7 @@
"outputs": [],
"source": [
"new_pipeline_conf = {\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.0\"},\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.1\"},\n",
" \"pipeline\": [\n",
" {\n",
" \"name\": \"segmentation\",\n",
Expand Down Expand Up @@ -759,7 +759,7 @@
"outputs": [],
"source": [
"default_pipeline_conf = {\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.0\"},\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.1\"},\n",
" \"pipeline\": [\n",
" {\n",
" \"name\": \"segmentation\",\n",
Expand Down Expand Up @@ -1034,7 +1034,7 @@
"outputs": [],
"source": [
"new_pipeline_conf = {\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.0\"},\n",
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.1.1\"},\n",
" \"pipeline\": [\n",
" {\n",
" \"name\": \"segmentation\",\n",
Expand Down
10 changes: 5 additions & 5 deletions docs/source/examples/custom_pipeline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ When the ``IRISPipeline`` pipeline is created with default parameters, it's grap
metadata:
pipeline_name: iris_pipeline
iris_version: 1.1.0
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.

Expand Down Expand Up @@ -93,7 +93,7 @@ First let's intantiate ``IRISPipeline`` with default configuration and see ``iri
.. code-block:: python
default_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.0"},
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
Expand Down Expand Up @@ -320,7 +320,7 @@ As expected all threshold values are set to default ``0.5`` value. Now, let's mo
.. code-block:: python
new_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.0"},
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
Expand Down Expand Up @@ -552,7 +552,7 @@ First let's instantiate ``IRISPipeline`` with default configuration and see node
.. code-block:: python
default_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.0"},
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
Expand Down Expand Up @@ -783,7 +783,7 @@ As expected, ``input_polygons`` argument of the ``run`` method is taken from the
.. code-block:: python
new_pipeline_conf = {
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.0"},
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.1.1"},
"pipeline": [
{
"name": "segmentation",
Expand Down
2 changes: 2 additions & 0 deletions src/iris/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
from iris.nodes.iris_response.probe_schemas.regular_probe_schema import RegularProbeSchema
from iris.nodes.iris_response_refinement.fragile_bits_refinement import FragileBitRefinement
from iris.nodes.matcher.hamming_distance_matcher import HammingDistanceMatcher
from iris.nodes.matcher.simple_hamming_distance_matcher import SimpleHammingDistanceMatcher
from iris.nodes.matcher.hamming_distance_matcher_interface import BatchMatcher, Matcher
from iris.nodes.normalization.linear_normalization import LinearNormalization
from iris.nodes.normalization.nonlinear_normalization import NonlinearNormalization
from iris.nodes.normalization.perspective_normalization import PerspectiveNormalization
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.1.0"
__version__ = "1.1.1"
31 changes: 19 additions & 12 deletions src/iris/nodes/matcher/hamming_distance_matcher.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from typing import List, Optional
from typing import List, Literal, Optional

import numpy as np
from pydantic import confloat, conint
from pydantic import confloat

from iris.io.class_configs import Algorithm
from iris.io.dataclasses import IrisTemplate
from iris.nodes.matcher.utils import hamming_distance
from iris.nodes.matcher.hamming_distance_matcher_interface import Matcher


class HammingDistanceMatcher(Algorithm):
class HammingDistanceMatcher(Matcher):
"""Hamming distance Matcher.
Algorithm steps:
Expand All @@ -18,32 +18,37 @@ class HammingDistanceMatcher(Algorithm):
4) If parameters nm_dist and weights are both defined, calculate weighted normalized Hamming distance (WNHD) based on IB_Counts, MB_Counts, nm_dist and weights.
5) Otherwise, calculate Hamming distance (HD) based on IB_Counts and MB_Counts.
6) If parameter rotation_shift is > 0, repeat the above steps for additional rotations of the iriscode.
7) Return the minimium distance from above calculations and its correpsonding rotation angle.
7) Return the minimium distance from above calculations.
"""

class Parameters(Algorithm.Parameters):
"""Parameters class for HammingDistanceMatcher."""
class Parameters(Matcher.Parameters):
"""IrisMatcherParameters parameters."""

rotation_shift: conint(ge=0, le=180, strict=True)
nm_dist: Optional[confloat(ge=0, le=1, strict=True)]
normalise: bool
nm_dist: confloat(ge=0, le=1, strict=True)
nm_type: Literal["linear", "sqrt"]
weights: Optional[List[np.ndarray]]

__parameters_type__ = Parameters

def __init__(
self,
rotation_shift: int = 15,
nm_dist: Optional[confloat(ge=0, le=1, strict=True)] = None,
normalise: bool = False,
nm_dist: confloat(ge=0, le=1, strict=True) = 0.45,
nm_type: Literal["linear", "sqrt"] = "sqrt",
weights: Optional[List[np.ndarray]] = None,
) -> None:
"""Assign parameters.
Args:
rotation_shift (int): rotations allowed in matching, converted to shifts in columns. Defaults to 15.
rotation_shift (int): rotations allowed in matching, experessed in iris code columns. Defaults to 15.
nm_dist (Optional[confloat(ge=0, le = 1, strict=True)]): nonmatch distance used for normalized HD. Optional paremeter for normalized HD. Defaults to None.
weights (Optional[List[np.ndarray]]): list of weights table. Optional paremeter for weighted HD. Defaults to None.
"""
super().__init__(rotation_shift=rotation_shift, nm_dist=nm_dist, weights=weights)
super().__init__(
rotation_shift=rotation_shift, normalise=normalise, nm_dist=nm_dist, nm_type=nm_type, weights=weights
)

def run(self, template_probe: IrisTemplate, template_gallery: IrisTemplate) -> float:
"""Match iris templates using Hamming distance.
Expand All @@ -59,7 +64,9 @@ def run(self, template_probe: IrisTemplate, template_gallery: IrisTemplate) -> f
template_probe,
template_gallery,
self.params.rotation_shift,
self.params.normalise,
self.params.nm_dist,
self.params.nm_type,
self.params.weights,
)

Expand Down
84 changes: 84 additions & 0 deletions src/iris/nodes/matcher/hamming_distance_matcher_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import abc
from typing import Any, List

from iris.io.class_configs import ImmutableModel
from iris.io.dataclasses import IrisTemplate
from pydantic import conint


class Matcher(abc.ABC):
"""Parent Abstract class for 1-to-1 matchers."""

class Parameters(ImmutableModel):
"""IrisMatcherParameters parameters."""

rotation_shift: conint(ge=0, strict=True)

__parameters_type__ = Parameters

def __init__(self, **kwargs) -> None:
"""Assign parameters.
Args:
rotation_shift (int = 15): rotation allowed in matching, converted to columns. Defaults to 15.
"""
self.params = self.__parameters_type__(**kwargs)

@abc.abstractmethod
def run(self, template_probe: IrisTemplate, template_gallery: IrisTemplate) -> float:
"""Match iris templates using Hamming distance.
Args:
template_probe (IrisTemplate): Iris template from probe.
template_gallery (IrisTemplate): Iris template from gallery.
Returns:
float: matching distance.
"""
pass


class BatchMatcher(abc.ABC):
"""Parent Abstract class for 1-to-N matchers."""

class Parameters(ImmutableModel):
"""IrisMatcherParameters parameters."""

rotation_shift: conint(ge=0, strict=True)

__parameters_type__ = Parameters

def __init__(self, **kwargs: Any) -> None:
"""Assign parameters.
Args:
rotation_shift (int = 15): rotation allowed in matching, converted to columns. Defaults to 15.
"""
self.params = self.__parameters_type__(**kwargs)

@abc.abstractmethod
def intra_gallery(self, template_gallery: List[IrisTemplate]) -> List[List[float]]:
"""Match iris templates using Hamming distance.
Args:
template_gallery (List[IrisTemplate]): Iris template gallery.
Returns:
List[List[float]]: matching distances.
"""
pass

@abc.abstractmethod
def gallery_to_gallery(
self, template_gallery_1: List[IrisTemplate], template_gallery_2: List[IrisTemplate]
) -> List[List[float]]:
"""Match iris templates using Hamming distance.
Args:
template_gallery_1 (List[IrisTemplate]): Iris template gallery.
template_gallery_2 (List[IrisTemplate]): Iris template gallery.
Returns:
List[List[float]]: matching distances.
"""
pass
66 changes: 66 additions & 0 deletions src/iris/nodes/matcher/simple_hamming_distance_matcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from pydantic import confloat, conint

from iris.io.dataclasses import IrisTemplate
from iris.nodes.matcher.utils import simple_hamming_distance
from iris.nodes.matcher.hamming_distance_matcher_interface import Matcher


class SimpleHammingDistanceMatcher(Matcher):
"""Hamming distance Matcher, without the bells and whistles.
Algorithm steps:
1) Calculate counts of nonmatch irisbits (IB_Counts) in common unmasked region and the counts of common maskbits (MB_Counts) in common unmasked region.
2) Calculate Hamming distance (HD) based on IB_Counts and MB_Counts.
3) If parameter `normalise` is True, normalize Hamming distance based on parameter `norm_mean` and parameter `norm_nb_bits`.
4) If parameter rotation_shift is > 0, repeat the above steps for additional rotations of the iriscode.
5) Return the minimium distance from above calculations.
"""

class Parameters(Matcher.Parameters):
"""IrisMatcherParameters parameters."""

normalise: bool
norm_mean: confloat(ge=0, le=1)
norm_nb_bits: conint(gt=0)

__parameters_type__ = Parameters

def __init__(
self,
rotation_shift: int = 15,
normalise: bool = False,
norm_mean: float = 0.45,
norm_nb_bits: float = 12288,
) -> None:
"""Assign parameters.
Args:
rotation_shift (int = 15): rotation allowed in matching, converted to columns. Defaults to 15.
normalise (bool = False): Flag to normalize HD. Defaults to False.
norm_mean (float = 0.45): Peak of the non-match distribution. Defaults to 0.45.
norm_nb_bits (float = 12288): Average number of bits visible in 2 randomly sampled iris codes. Defaults to 12288 (3/4 * total_bits_number for the iris code format v0.1).
"""
super().__init__(
rotation_shift=rotation_shift, normalise=normalise, norm_mean=norm_mean, norm_nb_bits=norm_nb_bits
)

def run(self, template_probe: IrisTemplate, template_gallery: IrisTemplate) -> float:
"""Match iris templates using Hamming distance.
Args:
template_probe (IrisTemplate): Iris template from probe.
template_gallery (IrisTemplate): Iris template from gallery.
Returns:
float: matching distance.
"""
score, _ = simple_hamming_distance(
template_probe=template_probe,
template_gallery=template_gallery,
rotation_shift=self.params.rotation_shift,
normalise=self.params.normalise,
norm_mean=self.params.norm_mean,
norm_nb_bits=self.params.norm_nb_bits,
)
return score
Loading

0 comments on commit 33036b2

Please sign in to comment.