Skip to content

Commit 9cd67fe

Browse files
authored
Merge pull request #164 from worldcoin/dev
Dev
2 parents baea011 + 0bacb06 commit 9cd67fe

17 files changed

Lines changed: 227 additions & 31 deletions

IRIS_PERFORMANCE_CARD.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A list of public and private entities participated in the ICE 2005 is shown belo
1010

1111
<img src="./docs/performance_card/ice2005_results.png" width="800" alt="ICE 2005 results">
1212

13-
Fig. 1(a) and 1(b) show the ROC on right and left eye, respectively, from all participants in Iris Challenge Evaluation (ICE) 2005 and Fig. 2(a) and 2(b) show our ROC in comparison*. Although the exact error rates were not published in [1], these plots show IRIS performs as good as if not better than commercial algorithms available at the time.
13+
Fig. 1(a) and 1(b) show the ROC on right and left eye, respectively, from all participants in Iris Challenge Evaluation (ICE) 2005 and Fig. 2(a) and 2(b) show our ROC in comparison*. Although the exact error rates were not published in [1], these plots show IRIS performs better than commercial algorithms available at the time.
1414

1515
|<img src="./docs/performance_card/comparison_1a.png" alt="1a"> Fig. 1(a) participant ROC on ICE 2005 (right eye) [1]| <img src="./docs/performance_card/comparison_1b.png" alt="1b"> Fig. 1(b) participant ROC on ICE 2005 (left eye) [1]|
1616
|---|---|
@@ -21,16 +21,16 @@ Fig. 1(a) and 1(b) show the ROC on right and left eye, respectively, from all pa
2121

2222
We also compare our performance with the latest available iris open source algorithm OSIRIS, which has three versions OSIRISV2, OSIRISV4, OSIRISV4.1 [3] as well as HDBIF [4]. As we can see in the table below, IRIS achieves significantly lower False NonMatch Rate (FNMR) at False Match Rate (FMR) of 0.001 and 0.0001 than these open source algorithms.
2323

24-
| | OSIRISV2 <br>[2009] | OSIRISV4 <br>[2011] | OSIRISV4.1 <br>[2016] | HDBIF <br>[2019] | IRIS\* <br>[2023] |
24+
| | OSIRISV2 <br>[2009] | OSIRISV4 <br>[2011] | OSIRISV4.1 <br>[2016] | HDBIF <br>[2019] | IRIS\* <br>[2025] |
2525
|-------------------|----------|----------|------------|-------|--------|
26-
| FNMR @ FMR=0.001 | 0.174 | 0.031 | 0.019 | 0.025 | 0.003 |
27-
| FNMR @ FMR=0.0001 | 0.268 | 0.058 | 0.034 | 0.039 | 0.006 |
26+
| FNMR @ FMR=0.001 | 0.174 | 0.031 | 0.019 | 0.025 | 0.0012 |
27+
| FNMR @ FMR=0.0001 | 0.268 | 0.058 | 0.034 | 0.039 | 0.0020 |
2828

29-
\*Note that four left eye images failed the default enrollment setting in IRIS with a Failure-To-Enroll (FTE) rate of 0.00135.
29+
\*Note that five left eye and two right eye images failed enrollment in IRIS with a Failure-To-Enroll (FTE) rate of 0.00238.
3030

3131
## ND-LG4000 dataset
3232

33-
ND-LG4000 dataset is part of the ND-CrossSensor-Iris-2013 public dataset from the University of Notre Dame [5]. This dataset was collected from 676 subjects using LG IrisAccess 4000 scanner with image resolution 640x480. We randomly chose 541 subjects for training, 67 subjects for validating the segmentation model in IRIS. The remaining 2187 images from 68 subjects were used for testing, resulting in a total of ~2.4 million matches.
33+
ND-LG4000 dataset is part of the ND-CrossSensor-Iris-2013 public dataset from the University of Notre Dame [5]. This dataset was collected from 676 subjects using LG IrisAccess 4000 scanner with image resolution 640x480. We randomly chose 541 subjects for training, 67 subjects for validating the segmentation model in IRIS. The remaining 2187 images from 68 subjects were used for testing, including 4 images failed in enrollment, resulting in a total of ~2.4 million matches.
3434

3535
|<img src="./docs/performance_card/lg4000_3a.png" alt="3a"> Fig. 3(a) IRIS ROC on ND-LG4000| <img src="./docs/performance_card/lg4000_3b.png" alt="3b"> Fig. 3(b) IRIS DET on ND-LG4000|
3636
|---|---|

colab/ConfiguringCustomPipeline.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
"```yaml\n",
127127
"metadata:\n",
128128
" pipeline_name: iris_pipeline\n",
129-
" iris_version: 1.9.4\n",
129+
" iris_version: 1.9.5\n",
130130
"```\n",
131131
"\n",
132132
"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",
@@ -206,7 +206,7 @@
206206
"outputs": [],
207207
"source": [
208208
"default_pipeline_conf = {\n",
209-
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.4\"},\n",
209+
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.5\"},\n",
210210
" \"pipeline\": [\n",
211211
" {\n",
212212
" \"name\": \"segmentation\",\n",
@@ -481,7 +481,7 @@
481481
"outputs": [],
482482
"source": [
483483
"new_pipeline_conf = {\n",
484-
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.4\"},\n",
484+
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.5\"},\n",
485485
" \"pipeline\": [\n",
486486
" {\n",
487487
" \"name\": \"segmentation\",\n",
@@ -760,7 +760,7 @@
760760
"outputs": [],
761761
"source": [
762762
"default_pipeline_conf = {\n",
763-
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.4\"},\n",
763+
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.5\"},\n",
764764
" \"pipeline\": [\n",
765765
" {\n",
766766
" \"name\": \"segmentation\",\n",
@@ -1035,7 +1035,7 @@
10351035
"outputs": [],
10361036
"source": [
10371037
"new_pipeline_conf = {\n",
1038-
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.4\"},\n",
1038+
" \"metadata\": {\"pipeline_name\": \"iris_pipeline\", \"iris_version\": \"1.9.5\"},\n",
10391039
" \"pipeline\": [\n",
10401040
" {\n",
10411041
" \"name\": \"segmentation\",\n",
-2.16 KB
Loading
-2.69 KB
Loading
-2.01 KB
Loading
-2.48 KB
Loading

docs/source/examples/custom_pipeline.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ When the ``IRISPipeline`` pipeline is created with default parameters, it's grap
1818
1919
metadata:
2020
pipeline_name: iris_pipeline
21-
iris_version: 1.9.4
21+
iris_version: 1.9.5
2222
2323
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.
2424

@@ -93,7 +93,7 @@ First let's intantiate ``IRISPipeline`` with default configuration and see ``iri
9393
.. code-block:: python
9494
9595
default_pipeline_conf = {
96-
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.4"},
96+
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.5"},
9797
"pipeline": [
9898
{
9999
"name": "segmentation",
@@ -320,7 +320,7 @@ As expected all threshold values are set to default ``0.5`` value. Now, let's mo
320320
.. code-block:: python
321321
322322
new_pipeline_conf = {
323-
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.4"},
323+
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.5"},
324324
"pipeline": [
325325
{
326326
"name": "segmentation",
@@ -552,7 +552,7 @@ First let's instantiate ``IRISPipeline`` with default configuration and see node
552552
.. code-block:: python
553553
554554
default_pipeline_conf = {
555-
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.4"},
555+
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.5"},
556556
"pipeline": [
557557
{
558558
"name": "segmentation",
@@ -783,7 +783,7 @@ As expected, ``input_polygons`` argument of the ``run`` method is taken from the
783783
.. code-block:: python
784784
785785
new_pipeline_conf = {
786-
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.4"},
786+
"metadata": {"pipeline_name": "iris_pipeline", "iris_version": "1.9.5"},
787787
"pipeline": [
788788
{
789789
"name": "segmentation",

src/iris/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.9.4"
1+
__version__ = "1.9.5"

src/iris/nodes/segmentation/multilabel_segmentation_interface.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,51 @@ def __init__(self, **kwargs: Any) -> None:
3838
"""Assign parameters."""
3939
super().__init__(**kwargs)
4040

41-
def preprocess(self, image: np.ndarray, input_resolution: Tuple[int, int], nn_input_channels: int) -> np.ndarray:
41+
def image_denoise(
42+
self, image: np.ndarray, d: int, sigmaColor: int, sigmaSpace: int, intensityIgnore: int
43+
) -> np.ndarray:
44+
"""Apply de-noising preprocessing step.
45+
46+
Args:
47+
image (np.ndarray): Image to denoise.
48+
d (int): Diameter of each pixel neighborhood that is used during filtering.
49+
sigmaColor (int): Filter sigma in color space.
50+
sigmaSpace (int): Filter sigma in coordinate space.
51+
intensityIgnore (int): Intensity threshold below which we do not apply filtering.
52+
53+
Returns:
54+
np.ndarray: Denoised image.
55+
"""
56+
# Bilateral filter to reduce noise while preserving edges.
57+
filtered_image = cv2.bilateralFilter(image, d=d, sigmaColor=sigmaColor, sigmaSpace=sigmaSpace)
58+
# Invert the mask to get the areas you do NOT want to filter
59+
inverse_mask = image < intensityIgnore
60+
filtered_region = filtered_image * (~inverse_mask)
61+
# Isolate the original, unfiltered region (where the inverse mask is white)
62+
original_region = image * inverse_mask
63+
# Combine the two regions to get the final image
64+
denoised_image = cv2.add(filtered_region, original_region)
65+
66+
return denoised_image
67+
68+
def preprocess(
69+
self, image: np.ndarray, input_resolution: Tuple[int, int], nn_input_channels: int, denoise: bool
70+
) -> np.ndarray:
4271
"""Preprocess image before running a model inference.
4372
4473
Args:
4574
image (np.ndarray): Image to preprocess.
4675
input_resolution (Tuple[int, int]): A model input resolution.
76+
denoise (bool): Whether to apply de-noising preprocessing step.
4777
nn_input_channels (int): A model input channels.
4878
4979
Returns:
5080
np.ndarray: Preprocessed image.
5181
"""
52-
nn_input = cv2.resize(image.astype(float), input_resolution)
82+
nn_input = cv2.resize(image.astype(float), input_resolution).astype(np.uint8)
83+
if denoise:
84+
nn_input = self.image_denoise(nn_input, d=5, sigmaColor=75, sigmaSpace=10, intensityIgnore=75)
85+
5386
nn_input = np.divide(nn_input, 255) # Replicates torchvision's ToTensor
5487

5588
nn_input = np.expand_dims(nn_input, axis=-1)

src/iris/nodes/segmentation/onnx_multilabel_segmentation.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Parameters(MultilabelSemanticSegmentationInterface.Parameters):
2626
session: ort.InferenceSession
2727
input_resolution: Tuple[PositiveInt, PositiveInt]
2828
input_num_channels: Literal[1, 3]
29+
denoise: bool
2930

3031
__parameters_type__ = Parameters
3132

@@ -35,6 +36,7 @@ def create_from_hugging_face(
3536
model_name: str = "iris_semseg_upp_scse_mobilenetv2.onnx",
3637
input_resolution: Tuple[PositiveInt, PositiveInt] = (640, 480),
3738
input_num_channels: Literal[1, 3] = 3,
39+
denoise: bool = False,
3840
callbacks: List[Callback] = [],
3941
) -> ONNXMultilabelSegmentation:
4042
"""Create ONNXMultilabelSegmentation object with by downloading model from HuggingFace repository `MultilabelSemanticSegmentationInterface.HUGGING_FACE_REPO_ID`.
@@ -43,6 +45,7 @@ def create_from_hugging_face(
4345
model_name (str, optional): Name of the ONNX model stored in HuggingFace repo. Defaults to "iris_semseg_upp_scse_mobilenetv2.onnx".
4446
input_resolution (Tuple[PositiveInt, PositiveInt], optional): Neural Network input image resolution. Defaults to (640, 480).
4547
input_num_channels (Literal[1, 3], optional): Neural Network input image number of channels. Defaults to 3.
48+
denoise (bool, optional): Whether to apply denoising preprocessing step. Defaults to False.
4649
callbacks (List[Callback], optional): List of algorithm callbacks. Defaults to [].
4750
4851
Returns:
@@ -55,13 +58,14 @@ def create_from_hugging_face(
5558
filename=model_name,
5659
)
5760

58-
return ONNXMultilabelSegmentation(model_path, input_resolution, input_num_channels, callbacks)
61+
return ONNXMultilabelSegmentation(model_path, input_resolution, input_num_channels, denoise, callbacks)
5962

6063
def __init__(
6164
self,
6265
model_path: str,
6366
input_resolution: Tuple[PositiveInt, PositiveInt] = (640, 480),
6467
input_num_channels: Literal[1, 3] = 3,
68+
denoise: bool = False,
6569
callbacks: List[Callback] = [],
6670
) -> None:
6771
"""Assign parameters.
@@ -70,6 +74,7 @@ def __init__(
7074
model_path (str): Path to the ONNX model.
7175
input_resolution (Tuple[PositiveInt, PositiveInt], optional): Neural Network input image resolution. Defaults to (640, 480).
7276
input_num_channels (Literal[1, 3], optional): Neural Network input image number of channels. Defaults to 3.
77+
denoise (bool, optional): Whether to apply denoising preprocessing step. Defaults to False.
7378
callbacks (List[Callback], optional): List of algorithm callbacks. Defaults to [].
7479
"""
7580
onnx_model = onnx.load(model_path)
@@ -79,6 +84,7 @@ def __init__(
7984
session=ort.InferenceSession(model_path, providers=["CPUExecutionProvider"]),
8085
input_resolution=input_resolution,
8186
input_num_channels=input_num_channels,
87+
denoise=denoise,
8288
callbacks=callbacks,
8389
)
8490

@@ -108,7 +114,9 @@ def _preprocess(self, image: np.ndarray) -> Dict[str, np.ndarray]:
108114
"""
109115
nn_input = image.copy()
110116

111-
nn_input = self.preprocess(nn_input, self.params.input_resolution, self.params.input_num_channels)
117+
nn_input = self.preprocess(
118+
nn_input, self.params.input_resolution, self.params.input_num_channels, self.params.denoise
119+
)
112120

113121
return {self.params.session.get_inputs()[0].name: nn_input.astype(np.float32)}
114122

0 commit comments

Comments
 (0)