Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
mmitchef authored Jul 9, 2024
1 parent d91da86 commit 5b18e09
Show file tree
Hide file tree
Showing 4 changed files with 620 additions and 36 deletions.
52 changes: 16 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,25 @@
# Privacy-Safe Iris Presentation Attack Detection
# Description

Official repository for the paper: Mahsa Mitcheff, Patrick Tinsley, Adam Czajka, "Privacy-Safe Iris Presentation Attack Detection," IEEE/IAPR International Joint Conference on Biometrics (IJCB), September 15-18, 2024, Buffalo, NY, USA
The code processes cropped iris images both with and without contact lenses as input, generating a PA score ranging from 0 to 1. A score of 0 indicates the sample without a contact lens, while a score of 1 signifies the sample with a contact lens.

Paper: IEEEXplore | ArXiv pre-print
# Training
```python train.py -csvPath csvFilePath -datasetPath datasetImagesPath -method modelName -outputPath resultPath```

## Abstract
The format of the dataset CSV file is as below:
<br>train,notcl,image1.png
<br>train,tcl,image2.png
<br>test,notcl,image3.png
<br>test,tcl,image4.png

This paper and repository propose a framework for a privacy-safe iris presentation attack detection (PAD) method, designed solely with synthetically-generated, identity-leakage-free iris images. Once trained, the method is evaluated in a classical way using state-of-the-art iris PAD benchmarks. We designed two generative models for the synthesis of ISO/IEC 19794-6-compliant iris images. The first model synthesizes bona fide-looking samples. To avoid “identity leakage,” the generated samples that accidentally matched those used in the model’s training were excluded. The second model synthesizes images of irises with textured contact lenses and is conditioned by a given contact lens brand to have better control over textured contact lens appearance when forming the training set. Our experiments demonstrate that models trained solely on synthetic data achieve a slightly lower but still reasonable performance when compared to solutions trained with iris images collected from human subjects. This is the first-of-its-kind attempt to use solely synthetic data to train a fully-functional iris PAD solution, and despite the performance gap between regular and the proposed methods, this study demonstrates that with the increasing fidelity of generative models, creating such privacy-safe iris PAD methods may be possible.
# Testing
After training the model, select the one with the highest accuracy on the validation set to evaluate its performance on unseen data

## Installation and Usage
```python test.py -csvPath csvFilePath -modelPath bestModelPth -trainData "synthetic" -model modelName -results resultPath -scoreFile "score.csv"```

...
A CSV file containing PA scores will be generated in the same folder as the images.

## Citations
___________________________________________________________________________________________
**Note:** To preserve anonymity during the review phase, instructions on how to obtain model weights and copy of the generated datasets will be added upon acceptance of the paper.

IJCB 2024 paper:

```
@InProceedings{Mitcheff_IJCB_2024,
author = {Mahsa Mitcheff and Patrick Tinsley and Adam Czajka},
booktitle = {The IEEE/IAPR International Joint Conference on Biometrics (IJCB)},
title = {{Privacy-Safe Iris Presentation Attack Detection}},
year = {2024},
address = {Buffalo, NY, USA},
month = {September 15-18},
pages = {1-8},
publisher = {IEEE}
}
```

This GitHub repository:

```
@Misc{ND_OpenSourceIrisRecognition_GitHub,
howpublished = {\url{https://github.com/CVRL/PrivacySafeIrisPAD/}},
note = {Accessed: X},
title = {{Privacy-Safe Iris Presentation Attack Detection (IJCB 2024 paper repository)}},
author = {Mahsa Mitcheff and Patrick Tinsley and Adam Czajka},
}
```

## License

...
The code was adopted from [DeNetPAD](https://github.com/iPRoBe-lab/D-NetPAD/tree/master).
250 changes: 250 additions & 0 deletions dataset_Loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import os
import tqdm
import numpy as np
import torch
import random
import torch.utils.data as data_utl
from PIL import Image, ImageFilter
import imgaug.augmenters as iaa
import torchvision.transforms as transforms
from torchvision.transforms import ToTensor, ToPILImage
import warnings
warnings.filterwarnings("ignore", category=FutureWarning)


class datasetLoader(data_utl.Dataset):

def __init__(self, split_file, root, method, train_test, random=True, c2i={}):

self.split_file = split_file
self.method = method
self.root = root
self.train_test = train_test
self.random = random
self.image_size = 229

# Image pre-processing
if self.train_test == 'test':
"""inception expects (299,299) sized images"""
if self.method == 'inception':
self.image_size = 229
else:
self.image_size = 224

self.transform_test = transforms.Compose([
transforms.Resize([self.image_size, self.image_size]),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485], std=[0.229])
])

# Class assignment
self.class_to_id = c2i
self.id_to_class = []
self.assign_classes()

# Data loading
self.get_data()

# Class assignment
def assign_classes(self):
for i in range(len(self.class_to_id.keys())):
for k in self.class_to_id.keys():
if self.class_to_id[k] == i:
self.id_to_class.append(k)

# Data loading (Reading data from CSV file)
def get_data(self):
self.data = []
print('Reading data from CSV file...', self.train_test)
cid = 0
with open(self.split_file, 'r') as f:
for l in f.readlines():
v = l.strip().split(',')
if self.train_test == v[0]:
image_name = v[2].replace(v[2].split('.')[-1], 'png')
imagePath = self.root + image_name
c = v[1]
if c not in self.class_to_id:
self.class_to_id[c] = cid
self.id_to_class.append(c)
cid += 1
# Storing data with image path and class
if os.path.exists(imagePath):
self.data.append([imagePath, self.class_to_id[c]])

def __len__(self):
return len(self.data)

def __getitem__(self, index):
# get image path, image name, and class from data
imagePath, cls = self.data[index]
imageName = imagePath.split('/')[-1]
path = imagePath

#####################################################################################
############### Read the train data, do pre-processing and augmentation #############
#####################################################################################
if self.train_test == 'train':

# Reading the image and convert it to gray image
img = Image.fromarray(np.array(Image.open(path).convert('RGB'))[:, :, 0], 'L')

# Resize the image
if self.method == 'inception':
img = img.resize((229, 229), Image.BILINEAR)
else:
img = img.resize((224, 224), Image.BILINEAR)

###########################################
########### Data augmentation #############
###########################################
# 1) horizontal flip
if random.random() < 0.5:
img = img.transpose(Image.FLIP_LEFT_RIGHT)

# 2) Affine transformation
aug = iaa.Affine(scale=(0.8,1.25), translate_px={"x": (-15, 15), "y": (-15, 15)}, rotate=(-30, 30), mode='edge')
img_np = aug(images = np.expand_dims(np.expand_dims(np.uint8(img), axis=0), axis=-1))
img = Image.fromarray(np.uint8(img_np[0, :, :, 0]))

# 3) Sharpening, 4)blurring, 5)Gaussian noise, 6)contrast change, 7)enhance brightness
if random.random() < 0.5:
random_choice = np.random.choice([1,2,3])

if random_choice == 1:
# 3) sharpening
random_degree = np.random.choice([1,2,3])
if random_degree == 1:
img = img.filter(ImageFilter.EDGE_ENHANCE)
elif random_degree == 2:
img= img.filter(ImageFilter.EDGE_ENHANCE_MORE)
elif random_degree == 3:
aug = iaa.Sharpen(alpha=(0.0, 0.3), lightness=(0.6, 1.0))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)

elif random_choice == 2:
# 4) blurring
random_degree = np.random.choice([1,2,3,4])
if random_degree == 1:
aug = iaa.GaussianBlur(sigma=(2.0,5.0))#(0.1, 1.0))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
elif random_degree == 2:
aug = iaa.imgcorruptlike.MotionBlur(severity=2)#1)
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
elif random_degree == 3:
aug = iaa.imgcorruptlike.GlassBlur(severity=2)#1)
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
elif random_degree == 4:
aug = iaa.imgcorruptlike.DefocusBlur(severity=3)#1)
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)

elif random_choice == 3:
# 5) AdditiveLaplaceNoise
if random.random() < 0.5:
aug = iaa.AdditiveLaplaceNoise(scale=(0, 3))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)


if random.random() < 0.5:
# 6) contrast change
random_degree = np.random.choice([1,2,3,4,5])
if random_degree == 1:
aug = iaa.GammaContrast((1.2, 1.8))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
elif random_degree == 2:
aug = iaa.LinearContrast((0.4, 1.75))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
elif random_degree == 3:
aug = iaa.SigmoidContrast(gain=(10, 12), cutoff=(0.5, 0.75))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
elif random_degree == 4:
aug = iaa.LogContrast(gain=(0.5, 1.4))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
else:
# 7) Enhance Brightness
aug = iaa.pillike.EnhanceBrightness(factor=(0.8, 1.9))
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)


if random.random() < 0.5:
# 8) sequence of randomness
random_degree = np.random.choice([1,2,3,4,5])
if random_degree == 1:
aug = iaa.Sequential([
iaa.GammaContrast((0.5, 2.0)),
iaa.pillike.EnhanceBrightness(),
iaa.GaussianBlur(sigma=(0.1,1.0))
])
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)
elif random_degree == 2:
aug = iaa.Sequential([
iaa.LinearContrast((0.4, 1.6)),
iaa.pillike.EnhanceBrightness(),
iaa.imgcorruptlike.MotionBlur(severity=1)
])
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)

elif random_degree == 3:
aug = iaa.Sequential([
iaa.LogContrast(gain=(0.6, 1.4)),
iaa.pillike.EnhanceBrightness(),
iaa.imgcorruptlike.GlassBlur(severity=1)
])
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)

elif random_degree == 4:
aug = iaa.Sequential([
iaa.SigmoidContrast(gain=(3, 10), cutoff=(0.4, 0.6)),
iaa.pillike.EnhanceBrightness(),
iaa.GaussianBlur(sigma=(0.1,2.0))
])
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)

else:
aug = iaa.Sequential([
iaa.pillike.EnhanceBrightness(factor=(0.1, 1.9)),
iaa.imgcorruptlike.DefocusBlur(severity=1)
])
img_np = aug(images = np.array(img))
img = Image.fromarray(img_np)


# convert PIL to tensor
pil_to_torch = ToTensor()
torch_img = pil_to_torch(img)
# Normalize the tensor
tranform_img = transforms.Normalize(mean=[0.485],std=[0.229])(torch_img)

elif self.train_test == 'test':
# Reading of the image and apply transformation
img = Image.open(path)
tranform_img = self.transform_test(img)

img.close()

# Repeat NIR single channel thrice before feeding into the network
tranform_img= tranform_img.repeat(3,1,1)

return tranform_img[0:3,:,:], cls, imageName

#if __name__ == '__main__':

# dataseta = datasetLoader('../TempData/Iris_OCT_Splits_Val/test_train_split.csv', 'PathToDatasetFolder', train_test='train')

# for i in range(len(dataseta)):
# print(len(dataseta.data))
Loading

0 comments on commit 5b18e09

Please sign in to comment.