tagny commited on
Commit
fa707a9
1 Parent(s): 7bed19a

team14: verio - working version 1

Browse files
app.py CHANGED
@@ -1,37 +1,49 @@
1
  """A local gradio app that detect matching images using FHE."""
2
 
3
- from PIL import Image
4
  import os
 
5
  import shutil
6
- import subprocess
7
  import time
8
- import gradio as gr
9
- import numpy
10
  import requests
 
 
 
 
 
11
  from itertools import chain
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  from common import (
14
- AVAILABLE_MATCHERS,
15
  CLIENT_TMP_PATH,
16
- ENCRYPTED_OUTPUT_NAME,
17
- ENCRYPTED_QUERY_NAME,
18
- ENCRYPTED_REFERENCE_NAME,
19
- SERVER_TMP_PATH,
20
- EXAMPLES,
21
- MATCHERS_PATH,
22
- INPUT_SHAPE,
23
  KEYS_PATH,
 
24
  REPO_DIR,
 
25
  SERVER_URL,
26
  )
27
- from client_server_interface import FHEClient
 
 
28
 
29
  # Uncomment here to have both the server and client in the same terminal
30
  subprocess.Popen(["uvicorn", "server:app"], cwd=REPO_DIR)
31
  time.sleep(3)
32
 
33
 
34
- def decrypt_output_with_wrong_key(encrypted_image, matcher_name):
35
  """Decrypt the encrypted output using a different private key."""
36
  # Retrieve the matcher's deployment path
37
  matcher_path = MATCHERS_PATH / f"{matcher_name}/deployment"
@@ -80,35 +92,31 @@ def shorten_bytes_object(bytes_object, limit=500):
80
  return bytes_object[shift : limit + shift].hex()
81
 
82
 
83
- def get_client(user_id, matcher_name):
84
  """Get the client API.
85
 
86
  Args:
87
  user_id (int): The current user's ID.
88
- matcher_name (str): The matcher chosen by the user
89
 
90
  Returns:
91
  FHEClient: The client API.
92
  """
93
- return FHEClient(
94
- MATCHERS_PATH / f"{matcher_name}/deployment",
95
- matcher_name,
96
- key_dir=KEYS_PATH / f"{matcher_name}_{user_id}",
97
- )
98
 
99
 
100
- def get_client_file_path(name, user_id, matcher_name):
101
  """Get the correct temporary file path for the client.
102
 
103
  Args:
104
  name (str): The desired file name.
105
  user_id (int): The current user's ID.
106
- matcher_name (str): The matcher chosen by the user
107
 
108
  Returns:
109
  pathlib.Path: The file path.
110
  """
111
- return CLIENT_TMP_PATH / f"{name}_{matcher_name}_{user_id}"
112
 
113
 
114
  def clean_temporary_files(n_keys=20):
@@ -133,8 +141,8 @@ def clean_temporary_files(n_keys=20):
133
  shutil.rmtree(key_dir)
134
 
135
  # Get all the encrypted objects in the temporary folder
136
- client_files = CLIENT_TMP_PATH.iterdir()
137
- server_files = SERVER_TMP_PATH.iterdir()
138
 
139
  # Delete all files related to the ids whose keys were deleted
140
  for file in chain(client_files, server_files):
@@ -157,10 +165,11 @@ def keygen(matcher_name):
157
  clean_temporary_files()
158
 
159
  # Create an ID for the current user
160
- user_id = numpy.random.randint(0, 2**32)
 
161
 
162
  # Retrieve the client API
163
- client = get_client(user_id, matcher_name)
164
 
165
  # Generate a private key
166
  client.generate_private_and_evaluation_keys(force=True)
@@ -172,7 +181,7 @@ def keygen(matcher_name):
172
 
173
  # Save evaluation_key as bytes in a file as it is too large to pass through regular Gradio
174
  # buttons (see https://github.com/gradio-app/gradio/issues/1877)
175
- evaluation_key_path = get_client_file_path("evaluation_key", user_id, matcher_name)
176
 
177
  with evaluation_key_path.open("wb") as evaluation_key_file:
178
  evaluation_key_file.write(evaluation_key)
@@ -180,18 +189,124 @@ def keygen(matcher_name):
180
  return (user_id, True)
181
 
182
 
183
- def encrypt(
184
- user_id, input_image, matcher_name, encrypted_image_name: str = "encrypted_image"
185
- ):
186
- """Encrypt the given image for a specific user and matcher.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
  Args:
189
  user_id (int): The current user's ID.
190
- input_image (numpy.ndarray): The image to encrypt.
191
- matcher_name (str): The current matcher to consider.
192
- encrypted_image_name (str): how to name the encrypted image
193
- to distinguish between the query and the reference images.
194
- Defaults to "encrypted_image"
195
 
196
  Returns:
197
  (input_image, encrypted_image_short) (Tuple[bytes]): The encrypted image and one of its
@@ -201,85 +316,75 @@ def encrypt(
201
  if user_id == "":
202
  raise gr.Error("Please generate the private key first.")
203
 
204
- if input_image is None:
205
- raise gr.Error("Please choose an image first.")
 
206
 
207
- if input_image.shape[-1] not in {3, 4}:
208
- raise ValueError(
209
- f"Input image must have 3 channels (RGB) or 4 channels. Current shape: {input_image.shape}"
210
- )
211
 
212
- if input_image.shape[-1] == 4:
213
- # Discarding alpha channel from images stored as Numpy arrays
214
- # (reference https://stackoverflow.com/questions/35902302/discarding-alpha-channel-from-images-stored-as-numpy-arrays)
215
- input_image = input_image[:, :, :3]
216
 
217
- # Resize the image if it hasn't the shape (INPUT_SHAPE[0], INPUT_SHAPE[1], 3)
218
- if input_image.shape != (INPUT_SHAPE[0], INPUT_SHAPE[1], 3):
219
- input_image_pil = Image.fromarray(input_image)
220
- input_image_pil = input_image_pil.resize((INPUT_SHAPE[0], INPUT_SHAPE[1]))
221
- input_image = numpy.array(input_image_pil)
222
 
 
 
 
 
 
223
  # Retrieve the client API
224
- client = get_client(user_id, matcher_name)
225
 
226
  # Pre-process, encrypt and serialize the image
227
- encrypted_image = client.encrypt_serialize(input_image)
228
 
229
  # Save encrypted_image to bytes in a file, since too large to pass through regular Gradio
230
  # buttons, https://github.com/gradio-app/gradio/issues/1877
231
- encrypted_image_path = get_client_file_path(
232
- encrypted_image_name, user_id, matcher_name
233
- )
234
 
235
- with encrypted_image_path.open("wb") as encrypted_image_file:
236
  encrypted_image_file.write(encrypted_image)
237
 
238
  # Create a truncated version of the encrypted image for display
239
  encrypted_image_short = shorten_bytes_object(encrypted_image)
240
 
241
- return (resize_img(input_image), encrypted_image_short)
 
 
 
 
242
 
243
 
244
- def send_input(user_id, matcher_name):
245
- """Send the encrypted input images as well as the evaluation key to the server.
246
 
247
  Args:
248
  user_id (int): The current user's ID.
249
- matcher_name (str): The current matcher to consider.
250
  """
251
  # Get the evaluation key path
252
- evaluation_key_path = get_client_file_path("evaluation_key", user_id, matcher_name)
253
 
254
  if user_id == "" or not evaluation_key_path.is_file():
255
  raise gr.Error("Please generate the private key first.")
256
 
257
- encrypted_query_image_path = get_client_file_path(
258
- ENCRYPTED_QUERY_NAME, user_id, matcher_name
259
- )
260
-
261
- encrypted_reference_image_path = get_client_file_path(
262
- ENCRYPTED_REFERENCE_NAME, user_id, matcher_name
263
- )
264
 
265
- for encrypted_input_path in {
266
- encrypted_query_image_path,
267
- encrypted_reference_image_path,
268
- }:
269
- if not encrypted_input_path.is_file():
270
- raise gr.Error(
271
- f"Please generate the private key and then encrypt an image first: {encrypted_input_path}"
272
- )
273
 
274
  # Define the data and files to post
275
  data = {
276
  "user_id": user_id,
277
- "matcher": matcher_name,
278
  }
279
 
280
  files = [
281
- ("files", open(encrypted_query_image_path, "rb")),
282
- ("files", open(encrypted_reference_image_path, "rb")),
283
  ("files", open(evaluation_key_path, "rb")),
284
  ]
285
 
@@ -293,16 +398,15 @@ def send_input(user_id, matcher_name):
293
  return response.ok
294
 
295
 
296
- def run_fhe(user_id, matcher_name):
297
- """Apply the matcher on the encrypted image previously sent using FHE.
298
 
299
  Args:
300
  user_id (int): The current user's ID.
301
- matcher_name (str): The current matcher to consider.
302
  """
303
  data = {
304
  "user_id": user_id,
305
- "matcher": matcher_name,
306
  }
307
 
308
  # Trigger the FHE execution on the encrypted image previously sent
@@ -314,25 +418,22 @@ def run_fhe(user_id, matcher_name):
314
  if response.ok:
315
  return response.json()
316
  else:
317
- print(f"ERROR run_fhe: {response}")
318
-
319
- raise gr.Error("Please wait for the input images to be sent to the server.")
320
 
321
 
322
- def get_output(user_id, matcher_name):
323
- """Retrieve the encrypted output.
324
 
325
  Args:
326
  user_id (int): The current user's ID.
327
- matcher_name (str): The current filter to consider.
328
 
329
  Returns:
330
- encrypted_output_short (bytes): A representation of the encrypted result.
331
 
332
  """
333
  data = {
334
  "user_id": user_id,
335
- "matcher": matcher_name,
336
  }
337
 
338
  # Retrieve the encrypted output image
@@ -346,38 +447,40 @@ def get_output(user_id, matcher_name):
346
 
347
  # Save the encrypted output to bytes in a file as it is too large to pass through regular
348
  # Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
349
- encrypted_output_path = get_client_file_path(
350
- ENCRYPTED_OUTPUT_NAME, user_id, matcher_name
351
- )
352
 
353
  with encrypted_output_path.open("wb") as encrypted_output_file:
354
  encrypted_output_file.write(encrypted_output)
355
 
356
- # Decrypt the output using a different (wrong) key for display
357
- output_representation = decrypt_output_with_wrong_key(
358
- encrypted_output, matcher_name
359
- )
360
 
361
- return {
362
- encrypted_output_representation: gr.update(
363
- value=output_representation
364
- # value=resize_img(output_image_representation)
365
- )
366
- }
 
 
 
 
367
 
368
  else:
369
  raise gr.Error("Please wait for the FHE execution to be completed.")
370
 
371
 
372
- def decrypt_output(user_id, matcher_name):
373
  """Decrypt the result.
374
 
375
  Args:
376
  user_id (int): The current user's ID.
377
- matcher_name (str): The current matcher to consider.
378
 
379
  Returns:
380
- (output_image, False, False) ((Tuple[numpy.ndarray, bool, bool]): The decrypted output, as
381
  well as two booleans used for resetting Gradio checkboxes
382
 
383
  """
@@ -385,9 +488,7 @@ def decrypt_output(user_id, matcher_name):
385
  raise gr.Error("Please generate the private key first.")
386
 
387
  # Get the encrypted output path
388
- encrypted_output_path = get_client_file_path(
389
- ENCRYPTED_OUTPUT_NAME, user_id, matcher_name
390
- )
391
 
392
  if not encrypted_output_path.is_file():
393
  raise gr.Error("Please run the FHE execution first.")
@@ -397,25 +498,28 @@ def decrypt_output(user_id, matcher_name):
397
  encrypted_output = encrypted_output_file.read()
398
 
399
  # Retrieve the client API
400
- client = get_client(user_id, matcher_name)
401
 
402
  # Deserialize, decrypt and post-process the encrypted output
403
- decrypted_ouput = client.deserialize_decrypt_post_process(encrypted_output)
404
 
405
  print(f"Decrypted output: {decrypted_ouput.shape=}")
 
406
 
407
- return {output_result: gr.update(value=decrypted_ouput)}
 
 
408
 
409
 
410
  def resize_img(img, width=256, height=256):
411
  """Resize the image."""
412
- if img.dtype != numpy.uint8:
413
- img = img.astype(numpy.uint8)
414
  img_pil = Image.fromarray(img)
415
  # Resize the image
416
  resized_img_pil = img_pil.resize((width, height))
417
- # Convert back to a NumPy array
418
- return numpy.array(resized_img_pil)
419
 
420
 
421
  demo = gr.Blocks()
@@ -428,9 +532,9 @@ with demo:
428
  <!--p align="center">
429
  <img width=200 src="https://user-images.githubusercontent.com/5758427/197816413-d9cddad3-ba38-4793-847d-120975e1da11.png">
430
  </p-->
431
- <h1 align="center"> #ppaihackteam14 </h1>
432
- <h1 align="center">Biometric image matching Using Fully Homomorphic Encryption</h1>
433
  <p align="center">
 
434
  <a href="https://github.com/zama-ai/concrete-ml"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197972109-faaaff3e-10e2-4ab6-80f5-7531f7cfb08f.png">Concrete-ML</a>
435
 
436
  <a href="https://docs.zama.ai/concrete-ml"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197976802-fddd34c5-f59a-48d0-9bff-7ad1b00cb1fb.png">Documentation</a>
@@ -447,11 +551,11 @@ with demo:
447
 
448
  gr.Markdown("## Client side")
449
  gr.Markdown("### Step 1: Upload input images. ")
450
- gr.Markdown(
451
- f"The image will automatically be resized to shape ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}). "
452
- "The image here, however, is displayed in its original resolution. The true image used "
453
- "in this demo can be seen in Step 8."
454
- )
455
  gr.Markdown("The query image to certify.")
456
  with gr.Row():
457
  input_query_img = gr.Image(
@@ -463,8 +567,8 @@ with demo:
463
  interactive=True,
464
  )
465
 
466
- examples = gr.Examples(
467
- examples=EXAMPLES,
468
  inputs=[input_query_img],
469
  examples_per_page=5,
470
  label="Examples to use.",
@@ -480,28 +584,28 @@ with demo:
480
  interactive=True,
481
  )
482
 
483
- examples = gr.Examples(
484
- examples=EXAMPLES,
485
  inputs=[input_reference_img],
486
  examples_per_page=5,
487
  label="Examples to use.",
488
  )
489
 
490
- gr.Markdown("### Step 2: Choose your matcher.")
491
- matcher_name = gr.Dropdown(
492
- choices=AVAILABLE_MATCHERS,
493
- value="random guessing",
494
- label="Choose your matcher",
495
- interactive=True,
496
- )
497
-
498
- gr.Markdown("#### Notes")
499
- gr.Markdown(
500
- """
501
- - The private key is used to encrypt and decrypt the data and will never be shared.
502
- - No public key is required for these matcher operators.
503
- """
504
- )
505
 
506
  gr.Markdown("### Step 3: Generate the private key.")
507
  keygen_button = gr.Button("Generate the private key.")
@@ -510,35 +614,27 @@ with demo:
510
  keygen_checkbox = gr.Checkbox(label="Private key generated:", interactive=False)
511
 
512
  user_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
513
- encrypted_query_image = gr.Textbox(
514
- value=ENCRYPTED_QUERY_NAME,
515
- label="",
516
- max_lines=2,
517
- interactive=False,
518
- visible=False,
519
- )
520
- encrypted_reference_image = gr.Textbox(
521
- value=ENCRYPTED_REFERENCE_NAME,
522
- label="",
523
- max_lines=2,
524
- interactive=False,
525
- visible=False,
526
- )
527
-
528
- gr.Markdown("### Step 4: Encrypt the images using FHE.")
529
- encrypt_query_button = gr.Button("Encrypt the query image using FHE.")
530
-
531
- with gr.Row():
532
- encrypted_input_query = gr.Textbox(
533
- label="Encrypted input query representation:",
534
- max_lines=2,
535
- interactive=False,
536
- )
537
 
538
- encrypt_reference_button = gr.Button("Encrypt the reference image using FHE.")
539
  with gr.Row():
540
- encrypted_input_reference = gr.Textbox(
541
- label="Encrypted input reference representation:",
542
  max_lines=2,
543
  interactive=False,
544
  )
@@ -596,14 +692,14 @@ with demo:
596
  with gr.Row():
597
  original_query_image = gr.Image(
598
  input_query_img.value,
599
- label=f"Input query image ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
600
  interactive=False,
601
  height=256,
602
  width=256,
603
  )
604
  original_reference_image = gr.Image(
605
  input_reference_img.value,
606
- label=f"Input reference image ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
607
  interactive=False,
608
  height=256,
609
  width=256,
@@ -619,46 +715,36 @@ with demo:
619
  # Button to generate the private key
620
  keygen_button.click(
621
  keygen,
622
- inputs=[matcher_name],
623
  outputs=[user_id, keygen_checkbox],
624
  )
625
 
626
  # Button to encrypt input query on the client side
627
- encrypt_query_button.click(
628
  encrypt,
629
- inputs=[user_id, input_query_img, matcher_name, encrypted_query_image],
630
- outputs=[original_query_image, encrypted_input_query],
631
- )
632
-
633
- # Button to encrypt input reference on the client side
634
- encrypt_reference_button.click(
635
- encrypt,
636
- inputs=[user_id, input_reference_img, matcher_name, encrypted_reference_image],
637
- outputs=[original_reference_image, encrypted_input_reference],
638
  )
639
 
640
  # Button to send the encodings to the server using post method
641
- send_input_button.click(
642
- send_input, inputs=[user_id, matcher_name], outputs=[send_input_checkbox]
643
- )
644
 
645
  # Button to send the encodings to the server using post method
646
- execute_fhe_button.click(
647
- run_fhe, inputs=[user_id, matcher_name], outputs=[fhe_execution_time]
648
- )
649
 
650
  # Button to send the encodings to the server using post method
651
  get_output_button.click(
652
  get_output,
653
- inputs=[user_id, matcher_name],
654
  outputs=[encrypted_output_representation],
655
  )
656
 
657
  # Button to decrypt the output on the client side
658
  decrypt_button.click(
659
  decrypt_output,
660
- inputs=[user_id, matcher_name],
661
- outputs=[output_result, keygen_checkbox, send_input_checkbox],
 
662
  )
663
 
664
  gr.Markdown(
 
1
  """A local gradio app that detect matching images using FHE."""
2
 
 
3
  import os
4
+ from pathlib import Path
5
  import shutil
 
6
  import time
7
+ from typing import Tuple
 
8
  import requests
9
+
10
+ import numpy as np
11
+
12
+ import subprocess
13
+ import gradio as gr
14
  from itertools import chain
15
+ import matplotlib.pyplot as plt
16
+ import matplotlib.image as img
17
+ import numpy as np
18
+ from PIL import Image
19
+ import torch
20
+ import torchvision.transforms as transforms
21
+ import torchvision.models as models
22
+ import cv2
23
+ from facenet_pytorch import InceptionResnetV1
24
+ from concrete.ml.deployment import FHEModelClient, FHEModelServer
25
+ from client_server_interface import FHEClient
26
 
27
  from common import (
 
28
  CLIENT_TMP_PATH,
29
+ ID_EXAMPLES,
30
+ SELFIE_EXAMPLES,
 
 
 
 
 
31
  KEYS_PATH,
32
+ MATCHERS_PATH,
33
  REPO_DIR,
34
+ SERVER_TMP_PATH,
35
  SERVER_URL,
36
  )
37
+
38
+ MODEL_PATH = "client_server"
39
+ # CLIENT_TMP_PATH = "client_tmp"
40
 
41
  # Uncomment here to have both the server and client in the same terminal
42
  subprocess.Popen(["uvicorn", "server:app"], cwd=REPO_DIR)
43
  time.sleep(3)
44
 
45
 
46
+ def decrypt_output_with_wrong_key(encrypted_image):
47
  """Decrypt the encrypted output using a different private key."""
48
  # Retrieve the matcher's deployment path
49
  matcher_path = MATCHERS_PATH / f"{matcher_name}/deployment"
 
92
  return bytes_object[shift : limit + shift].hex()
93
 
94
 
95
+ def get_client():
96
  """Get the client API.
97
 
98
  Args:
99
  user_id (int): The current user's ID.
100
+ filter_name (str): The filter chosen by the user
101
 
102
  Returns:
103
  FHEClient: The client API.
104
  """
105
+ return FHEModelClient(MODEL_PATH)
 
 
 
 
106
 
107
 
108
+ def get_client_file_path(name, user_id):
109
  """Get the correct temporary file path for the client.
110
 
111
  Args:
112
  name (str): The desired file name.
113
  user_id (int): The current user's ID.
114
+ filter_name (str): The filter chosen by the user
115
 
116
  Returns:
117
  pathlib.Path: The file path.
118
  """
119
+ return CLIENT_TMP_PATH / f"{name}_embedding_{user_id}"
120
 
121
 
122
  def clean_temporary_files(n_keys=20):
 
141
  shutil.rmtree(key_dir)
142
 
143
  # Get all the encrypted objects in the temporary folder
144
+ client_files = Path(CLIENT_TMP_PATH).iterdir()
145
+ server_files = Path(SERVER_TMP_PATH).iterdir()
146
 
147
  # Delete all files related to the ids whose keys were deleted
148
  for file in chain(client_files, server_files):
 
165
  clean_temporary_files()
166
 
167
  # Create an ID for the current user
168
+ user_id = np.random.randint(0, 2**32)
169
+ # user_id = 298147048
170
 
171
  # Retrieve the client API
172
+ client = get_client()
173
 
174
  # Generate a private key
175
  client.generate_private_and_evaluation_keys(force=True)
 
181
 
182
  # Save evaluation_key as bytes in a file as it is too large to pass through regular Gradio
183
  # buttons (see https://github.com/gradio-app/gradio/issues/1877)
184
+ evaluation_key_path = get_client_file_path("evaluation_key", user_id)
185
 
186
  with evaluation_key_path.open("wb") as evaluation_key_file:
187
  evaluation_key_file.write(evaluation_key)
 
189
  return (user_id, True)
190
 
191
 
192
+ def detect_and_crop_face(
193
+ image: str,
194
+ min_aspect_ratio: float = 0.5,
195
+ max_aspect_ratio: float = 1.5,
196
+ min_face_size: float = 0.01,
197
+ max_face_size: float = 0.6,
198
+ ) -> Tuple[np.ndarray, Tuple[int, int, int, int], np.ndarray]:
199
+ # Read the image
200
+ # image = cv2.imread(image_path)
201
+ image_path = "test"
202
+ if image is None:
203
+ print(f"Failed to load image: {image_path}")
204
+ return None
205
+
206
+ # Print the image depth to debug
207
+ print(f"Image Depth: {image.dtype}, Shape: {image.shape}")
208
+
209
+ # Check if the image is of type CV_64F (float64) and convert to uint8
210
+ if image.dtype == np.float64:
211
+ print(f"Converting image from float64 to uint8 for {image_path}")
212
+ image = cv2.convertScaleAbs(image) # Scale and convert to 8-bit
213
+
214
+ elif image.dtype != np.uint8:
215
+ print(f"Converting image from {image.dtype} to uint8 for {image_path}")
216
+ image = cv2.convertScaleAbs(image) # Convert to 8-bit unsigned
217
+
218
+ # Convert to grayscale
219
+ try:
220
+ gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
221
+ except cv2.error as e:
222
+ print(f"Error converting image to grayscale: {e} for {image_path}")
223
+ return None
224
+
225
+ # Load the face classifier
226
+ face_classifier = cv2.CascadeClassifier(
227
+ cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
228
+ )
229
+
230
+ # Detect faces
231
+ faces = face_classifier.detectMultiScale(
232
+ gray_image,
233
+ scaleFactor=1.1,
234
+ minNeighbors=5,
235
+ minSize=(int(image.shape[1] * 0.1), int(image.shape[0] * 0.1)),
236
+ )
237
+
238
+ valid_faces = []
239
+ for x, y, w, h in faces:
240
+ aspect_ratio = w / h
241
+ face_area = w * h
242
+ image_area = image.shape[0] * image.shape[1]
243
+ face_size_ratio = face_area / image_area
244
+
245
+ if (
246
+ min_aspect_ratio <= aspect_ratio <= max_aspect_ratio
247
+ and min_face_size <= face_size_ratio <= max_face_size
248
+ ):
249
+ valid_faces.append((x, y, w, h))
250
+
251
+ if not valid_faces:
252
+ print(f"No suitable faces detected in {image_path}")
253
+ return None
254
+
255
+ # Sort faces by area (descending) and select the largest
256
+ valid_faces.sort(key=lambda f: f[2] * f[3], reverse=True)
257
+ (x, y, w, h) = valid_faces[0]
258
+
259
+ # Crop the face
260
+ try:
261
+ face_crop = image[
262
+ int(y - h * 0.1) : int(y + h * 1.1), int(x - w * 0.1) : int(x + w * 1.1)
263
+ ]
264
+ if face_crop.size == 0:
265
+ print(f"Failed to crop face for {image_path}: resulting crop is empty")
266
+ return None
267
+ except Exception as e:
268
+ print(f"Error cropping face from {image_path}: {e}")
269
+ return None
270
+
271
+ # Convert to RGB for display
272
+ try:
273
+ face_crop_rgb = cv2.cvtColor(face_crop, cv2.COLOR_BGR2RGB)
274
+ except cv2.error as e:
275
+ print(f"Error converting cropped face to RGB: {e} for {image_path}")
276
+ return None
277
+
278
+ return face_crop_rgb, (x, y, w, h), image
279
+
280
+
281
+ def preprocess_image(input_image):
282
+ # TODO change for facenet
283
+ model = InceptionResnetV1(pretrained="vggface2").eval()
284
+ input_image = np.array(input_image)
285
+ image_crop = detect_and_crop_face(image=input_image)
286
+ preprocess = transforms.Compose(
287
+ [
288
+ transforms.Resize((160, 160)), # Resize to 160x160 as required by the model
289
+ transforms.ToTensor(), # Convert to tensor
290
+ transforms.Normalize(
291
+ [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]
292
+ ), # Normalize to [-1, 1]
293
+ ]
294
+ )
295
+ if image_crop[0] is not None:
296
+ img_tensor = preprocess(Image.fromarray(image_crop[0]))
297
+ img_tensor = img_tensor.unsqueeze(0)
298
+ with torch.no_grad():
299
+ embedding = model(img_tensor)
300
+ return embedding.numpy().flatten()
301
+
302
+
303
+ def encrypt(user_id, selfie_image, id_image):
304
+ """Encrypt the given image for a specific user and filter.
305
 
306
  Args:
307
  user_id (int): The current user's ID.
308
+ selfie_image (np.ndarray): The image to encrypt.
309
+ id_image (np.ndarray): The image to encrypt.
 
 
 
310
 
311
  Returns:
312
  (input_image, encrypted_image_short) (Tuple[bytes]): The encrypted image and one of its
 
316
  if user_id == "":
317
  raise gr.Error("Please generate the private key first.")
318
 
319
+ # for input_image in [selfie_image, id_image]:
320
+ # if input_image is None:
321
+ # raise gr.Error("Please choose an image first.")
322
 
323
+ # if input_image.shape[-1] != 3:
324
+ # raise ValueError(
325
+ # f"Input image must have 3 channels (RGB). Current shape: {input_image.shape}"
326
+ # )
327
 
328
+ # Resize the image if it hasn't the shape (100, 100, 3)
 
 
 
329
 
330
+ selfie_image_orig = selfie_image.copy()
331
+ id_image_orig = id_image.copy()
 
 
 
332
 
333
+ selfie_image = Image.fromarray(selfie_image).convert("RGB")
334
+ id_image = Image.fromarray(id_image).convert("RGB")
335
+ embeddings_selfie = preprocess_image(selfie_image)
336
+ embeddings_id = preprocess_image(id_image)
337
+ X = np.concatenate((embeddings_selfie, embeddings_id))[np.newaxis, ...]
338
  # Retrieve the client API
339
+ client: FHEModelClient = get_client()
340
 
341
  # Pre-process, encrypt and serialize the image
342
+ encrypted_image = client.quantize_encrypt_serialize(X)
343
 
344
  # Save encrypted_image to bytes in a file, since too large to pass through regular Gradio
345
  # buttons, https://github.com/gradio-app/gradio/issues/1877
346
+ encrypted_embedding = get_client_file_path("encrypted_embedding", user_id)
 
 
347
 
348
+ with encrypted_embedding.open("wb") as encrypted_image_file:
349
  encrypted_image_file.write(encrypted_image)
350
 
351
  # Create a truncated version of the encrypted image for display
352
  encrypted_image_short = shorten_bytes_object(encrypted_image)
353
 
354
+ return (
355
+ encrypted_image_short,
356
+ resize_img(selfie_image_orig),
357
+ resize_img(id_image_orig),
358
+ )
359
 
360
 
361
+ def send_input(user_id):
362
+ """Send the encrypted input image as well as the evaluation key to the server.
363
 
364
  Args:
365
  user_id (int): The current user's ID.
366
+ filter_name (str): The current filter to consider.
367
  """
368
  # Get the evaluation key path
369
+ evaluation_key_path = get_client_file_path("evaluation_key", user_id)
370
 
371
  if user_id == "" or not evaluation_key_path.is_file():
372
  raise gr.Error("Please generate the private key first.")
373
 
374
+ encrypted_input_path = get_client_file_path("encrypted_embedding", user_id)
 
 
 
 
 
 
375
 
376
+ if not encrypted_input_path.is_file():
377
+ raise gr.Error(
378
+ "Please generate the private key and then encrypt an image first."
379
+ )
 
 
 
 
380
 
381
  # Define the data and files to post
382
  data = {
383
  "user_id": user_id,
 
384
  }
385
 
386
  files = [
387
+ ("files", open(encrypted_input_path, "rb")),
 
388
  ("files", open(evaluation_key_path, "rb")),
389
  ]
390
 
 
398
  return response.ok
399
 
400
 
401
+ def run_fhe(user_id):
402
+ """Apply the filter on the encrypted image previously sent using FHE.
403
 
404
  Args:
405
  user_id (int): The current user's ID.
406
+ filter_name (str): The current filter to consider.
407
  """
408
  data = {
409
  "user_id": user_id,
 
410
  }
411
 
412
  # Trigger the FHE execution on the encrypted image previously sent
 
418
  if response.ok:
419
  return response.json()
420
  else:
421
+ raise gr.Error("Please wait for the input image to be sent to the server.")
 
 
422
 
423
 
424
+ def get_output(user_id):
425
+ """Retrieve the encrypted output image.
426
 
427
  Args:
428
  user_id (int): The current user's ID.
429
+ filter_name (str): The current filter to consider.
430
 
431
  Returns:
432
+ encrypted_output_image_short (bytes): A representation of the encrypted result.
433
 
434
  """
435
  data = {
436
  "user_id": user_id,
 
437
  }
438
 
439
  # Retrieve the encrypted output image
 
447
 
448
  # Save the encrypted output to bytes in a file as it is too large to pass through regular
449
  # Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
450
+ encrypted_output_path = get_client_file_path("encrypted_output", user_id)
 
 
451
 
452
  with encrypted_output_path.open("wb") as encrypted_output_file:
453
  encrypted_output_file.write(encrypted_output)
454
 
455
+ # # Decrypt the image using a different (wrong) key for display
456
+ # output_image_representation = decrypt_output_with_wrong_key(
457
+ # encrypted_output
458
+ # )
459
 
460
+ # return {
461
+ # encrypted_output_representation: gr.update(
462
+ # value=resize_img(output_image_representation)
463
+ # )
464
+ # }
465
+
466
+ # Create a truncated version of the encrypted image for display
467
+ encrypted_output_short = shorten_bytes_object(encrypted_output)
468
+
469
+ return encrypted_output_short
470
 
471
  else:
472
  raise gr.Error("Please wait for the FHE execution to be completed.")
473
 
474
 
475
+ def decrypt_output(user_id):
476
  """Decrypt the result.
477
 
478
  Args:
479
  user_id (int): The current user's ID.
480
+ filter_name (str): The current filter to consider.
481
 
482
  Returns:
483
+ (output_image, False, False) ((Tuple[np.ndarray, bool, bool]): The decrypted output, as
484
  well as two booleans used for resetting Gradio checkboxes
485
 
486
  """
 
488
  raise gr.Error("Please generate the private key first.")
489
 
490
  # Get the encrypted output path
491
+ encrypted_output_path = get_client_file_path("encrypted_output", user_id)
 
 
492
 
493
  if not encrypted_output_path.is_file():
494
  raise gr.Error("Please run the FHE execution first.")
 
498
  encrypted_output = encrypted_output_file.read()
499
 
500
  # Retrieve the client API
501
+ client = get_client()
502
 
503
  # Deserialize, decrypt and post-process the encrypted output
504
+ decrypted_ouput = client.deserialize_decrypt_dequantize(encrypted_output)
505
 
506
  print(f"Decrypted output: {decrypted_ouput.shape=}")
507
+ print(f"Decrypted output: {decrypted_ouput=}")
508
 
509
+ predicted_class_id = np.argmax(decrypted_ouput)
510
+ print(f"{predicted_class_id=}")
511
+ return "PASS" if predicted_class_id == 1 else "FAIL"
512
 
513
 
514
  def resize_img(img, width=256, height=256):
515
  """Resize the image."""
516
+ if img.dtype != np.uint8:
517
+ img = img.astype(np.uint8)
518
  img_pil = Image.fromarray(img)
519
  # Resize the image
520
  resized_img_pil = img_pil.resize((width, height))
521
+ # Convert back to a np array
522
+ return np.array(resized_img_pil)
523
 
524
 
525
  demo = gr.Blocks()
 
532
  <!--p align="center">
533
  <img width=200 src="https://user-images.githubusercontent.com/5758427/197816413-d9cddad3-ba38-4793-847d-120975e1da11.png">
534
  </p-->
535
+ <h1 align="center">Verio “Privacy-Preserving Biometric Verification for Authentication”</h1>
 
536
  <p align="center">
537
+ #ppaihackteam14
538
  <a href="https://github.com/zama-ai/concrete-ml"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197972109-faaaff3e-10e2-4ab6-80f5-7531f7cfb08f.png">Concrete-ML</a>
539
 
540
  <a href="https://docs.zama.ai/concrete-ml"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197976802-fddd34c5-f59a-48d0-9bff-7ad1b00cb1fb.png">Documentation</a>
 
551
 
552
  gr.Markdown("## Client side")
553
  gr.Markdown("### Step 1: Upload input images. ")
554
+ # gr.Markdown(
555
+ # f"The image will automatically be resized to shape ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}). "
556
+ # "The image here, however, is displayed in its original resolution. The true image used "
557
+ # "in this demo can be seen in Step 8."
558
+ # )
559
  gr.Markdown("The query image to certify.")
560
  with gr.Row():
561
  input_query_img = gr.Image(
 
567
  interactive=True,
568
  )
569
 
570
+ selfie_examples = gr.Examples(
571
+ examples=SELFIE_EXAMPLES,
572
  inputs=[input_query_img],
573
  examples_per_page=5,
574
  label="Examples to use.",
 
584
  interactive=True,
585
  )
586
 
587
+ id_examples = gr.Examples(
588
+ examples=ID_EXAMPLES,
589
  inputs=[input_reference_img],
590
  examples_per_page=5,
591
  label="Examples to use.",
592
  )
593
 
594
+ # gr.Markdown("### Step 2: Choose your matcher.")
595
+ # matcher_name = gr.Dropdown(
596
+ # choices=AVAILABLE_MATCHERS,
597
+ # value="random guessing",
598
+ # label="Choose your matcher",
599
+ # interactive=True,
600
+ # )
601
+
602
+ # gr.Markdown("#### Notes")
603
+ # gr.Markdown(
604
+ # """
605
+ # - The private key is used to encrypt and decrypt the data and will never be shared.
606
+ # - No public key is required for these matcher operators.
607
+ # """
608
+ # )
609
 
610
  gr.Markdown("### Step 3: Generate the private key.")
611
  keygen_button = gr.Button("Generate the private key.")
 
614
  keygen_checkbox = gr.Checkbox(label="Private key generated:", interactive=False)
615
 
616
  user_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
617
+ # encrypted_query_image = gr.Textbox(
618
+ # value=ENCRYPTED_QUERY_NAME,
619
+ # label="",
620
+ # max_lines=2,
621
+ # interactive=False,
622
+ # visible=False,
623
+ # )
624
+ # encrypted_reference_image = gr.Textbox(
625
+ # value=ENCRYPTED_REFERENCE_NAME,
626
+ # label="",
627
+ # max_lines=2,
628
+ # interactive=False,
629
+ # visible=False,
630
+ # )
631
+
632
+ gr.Markdown("### Step 4: Encrypt the input images using FHE.")
633
+ encrypt_button = gr.Button("Encrypt the images using FHE.")
 
 
 
 
 
 
 
634
 
 
635
  with gr.Row():
636
+ encrypted_input = gr.Textbox(
637
+ label="Encrypted input images representation:",
638
  max_lines=2,
639
  interactive=False,
640
  )
 
692
  with gr.Row():
693
  original_query_image = gr.Image(
694
  input_query_img.value,
695
+ label=f"Input query image:",
696
  interactive=False,
697
  height=256,
698
  width=256,
699
  )
700
  original_reference_image = gr.Image(
701
  input_reference_img.value,
702
+ label=f"Input reference image:",
703
  interactive=False,
704
  height=256,
705
  width=256,
 
715
  # Button to generate the private key
716
  keygen_button.click(
717
  keygen,
718
+ inputs=[],
719
  outputs=[user_id, keygen_checkbox],
720
  )
721
 
722
  # Button to encrypt input query on the client side
723
+ encrypt_button.click(
724
  encrypt,
725
+ inputs=[user_id, input_query_img, input_reference_img],
726
+ outputs=[encrypted_input, original_query_image, original_reference_image],
 
 
 
 
 
 
 
727
  )
728
 
729
  # Button to send the encodings to the server using post method
730
+ send_input_button.click(send_input, inputs=[user_id], outputs=[send_input_checkbox])
 
 
731
 
732
  # Button to send the encodings to the server using post method
733
+ execute_fhe_button.click(run_fhe, inputs=[user_id], outputs=[fhe_execution_time])
 
 
734
 
735
  # Button to send the encodings to the server using post method
736
  get_output_button.click(
737
  get_output,
738
+ inputs=[user_id],
739
  outputs=[encrypted_output_representation],
740
  )
741
 
742
  # Button to decrypt the output on the client side
743
  decrypt_button.click(
744
  decrypt_output,
745
+ inputs=[user_id],
746
+ # outputs=[output_result, original_query_image, original_reference_image],
747
+ outputs=[output_result],
748
  )
749
 
750
  gr.Markdown(
client_server/client.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:71104d20cae502527c0e5f9f25813c5d339c2629aa640fd12d53ffcb8c78c4d5
3
+ size 1115569
matchers/filter blur/deployment/client.zip → client_server/server.zip RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c6b45511096f6da9aa73601b1372ffc42ec9272b40348d92f90427fbddff0571
3
- size 360
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9351a05f346e608a2263f0c6e77895ba9c0659151ae761251e21e39d49bb6853
3
+ size 17205
common.py CHANGED
@@ -33,7 +33,10 @@ INPUT_SHAPE = (100, 100)
33
  INPUT_EXAMPLES_DIR = REPO_DIR / "input_examples"
34
 
35
  # List of all image examples suggested in the demo
36
- EXAMPLES = [str(image) for image in INPUT_EXAMPLES_DIR.glob("**/*")]
 
 
 
37
 
38
  # Encrypted image and output names
39
  ENCRYPTED_QUERY_NAME = "encrypted_query_image"
 
33
  INPUT_EXAMPLES_DIR = REPO_DIR / "input_examples"
34
 
35
  # List of all image examples suggested in the demo
36
+ ID_EXAMPLES = [str(image) for image in (INPUT_EXAMPLES_DIR / "ids").glob("**/*")]
37
+ SELFIE_EXAMPLES = [
38
+ str(image) for image in (INPUT_EXAMPLES_DIR / "selfies").glob("**/*")
39
+ ]
40
 
41
  # Encrypted image and output names
42
  ENCRYPTED_QUERY_NAME = "encrypted_query_image"
generate_deployment_files.py CHANGED
@@ -8,7 +8,7 @@ print("Generating deployment files for all available filters")
8
  # This repository's directory
9
  REPO_DIR = Path(__file__).parent
10
  # This repository's main necessary folders
11
- MATCHERS_PATH = REPO_DIR / "data/compiled_models"
12
  for matcher_name in AVAILABLE_MATCHERS:
13
  print("Matcher:", matcher_name, "\n")
14
 
 
8
  # This repository's directory
9
  REPO_DIR = Path(__file__).parent
10
  # This repository's main necessary folders
11
+ MATCHERS_PATH = REPO_DIR / "matchers"
12
  for matcher_name in AVAILABLE_MATCHERS:
13
  print("Matcher:", matcher_name, "\n")
14
 
input_examples/1.png DELETED
Binary file (16.6 kB)
 
input_examples/2.png DELETED
Binary file (18.7 kB)
 
input_examples/3.png DELETED
Binary file (18.5 kB)
 
input_examples/4.png DELETED
Binary file (24.2 kB)
 
input_examples/5.png DELETED
Binary file (22.7 kB)
 
input_examples/ids/ID_5.jpg ADDED

Git LFS Details

  • SHA256: a84961a3b757ffbfdd538ba4010cacd4d5b87abca90f60bca17cf875f5352a8e
  • Pointer size: 131 Bytes
  • Size of remote file: 221 kB
input_examples/ids/ID_6.jpg ADDED

Git LFS Details

  • SHA256: 646fcd997c41dabd18d51a11fc56520f4e02d908cf38196149e982158e60cb9d
  • Pointer size: 131 Bytes
  • Size of remote file: 225 kB
input_examples/ids/ID_7.jpg ADDED

Git LFS Details

  • SHA256: 91c70c6cb42c136ef082f51f8de26b64ccbc30c8d5bc15b35786819f6a6a245c
  • Pointer size: 131 Bytes
  • Size of remote file: 331 kB
input_examples/selfies/selfie_5.jpg ADDED

Git LFS Details

  • SHA256: 8e41075d99b388d9ce6a692f508f677b6ebe2b6f5fb8ec11de5bb632298e8298
  • Pointer size: 131 Bytes
  • Size of remote file: 336 kB
input_examples/selfies/selfie_6.jpg ADDED

Git LFS Details

  • SHA256: 5ab14736f60fea23d8504565b8e120dddae1abcfe612a08c95d485d84b891f04
  • Pointer size: 131 Bytes
  • Size of remote file: 650 kB
input_examples/selfies/selfie_7.jpg ADDED

Git LFS Details

  • SHA256: a18d7b86a40e132d026c594583d59de55b14d2a392af84b57202bb8470b2c0e3
  • Pointer size: 131 Bytes
  • Size of remote file: 492 kB
matchers.py CHANGED
@@ -17,19 +17,17 @@ class TorchRandomGuessing(nn.Module):
17
  super().__init__()
18
  self.classes_ = classes_
19
 
20
- def forward(self, q, r):
21
  """Random guessing forward pass.
22
 
23
  Args:
24
- q (torch.Tensor): The input query.
25
- r (torch.Tensor): The input reference.
26
 
27
  Returns:
28
  (torch.Tensor): .
29
  """
30
- q = q.sum()
31
- r = r.sum()
32
- return torch.tensor([random.choice([0, 1])]) + q - q + r - r
33
 
34
 
35
  class Matcher:
@@ -46,17 +44,14 @@ class Matcher:
46
 
47
  def compile(self):
48
 
49
- inputset = [(np.array([10]), np.array([5]))]
50
 
51
  print("torch module > numpy module ...")
52
  numpy_module = NumpyModule(
53
  # torch_model, dummy_input=torch.from_numpy(np.array([10], dtype=np.int64))
54
  self.torch_model,
55
  # dummy_input=(torch.tensor([10]), torch.tensor([5])),
56
- dummy_input=(
57
- torch.from_numpy(inputset[0][1]),
58
- torch.from_numpy(inputset[0][1]),
59
- ),
60
  )
61
 
62
  print("get proxy function ...")
@@ -64,15 +59,14 @@ class Matcher:
64
  # This is done in order to be able to provide any modules with arbitrary numbers of
65
  # encrypted arguments to Concrete Numpy's compiler
66
  numpy_filter_proxy, parameters_mapping = generate_proxy_function(
67
- numpy_module.numpy_forward, ["query", "reference"]
68
  )
69
 
70
  print("Compile the filter and retrieve its FHE circuit ...")
71
  compiler = Compiler(
72
  numpy_filter_proxy,
73
  {
74
- parameters_mapping["query"]: "encrypted",
75
- parameters_mapping["reference"]: "encrypted",
76
  },
77
  )
78
  self.fhe_circuit = compiler.compile(inputset)
 
17
  super().__init__()
18
  self.classes_ = classes_
19
 
20
+ def forward(self, x):
21
  """Random guessing forward pass.
22
 
23
  Args:
24
+ x (torch.Tensor): concat of query and reference.
 
25
 
26
  Returns:
27
  (torch.Tensor): .
28
  """
29
+ x = x.sum()
30
+ return torch.tensor([random.choice([0, 1])]) + x - x
 
31
 
32
 
33
  class Matcher:
 
44
 
45
  def compile(self):
46
 
47
+ inputset = (np.array([10]), np.array([5]))
48
 
49
  print("torch module > numpy module ...")
50
  numpy_module = NumpyModule(
51
  # torch_model, dummy_input=torch.from_numpy(np.array([10], dtype=np.int64))
52
  self.torch_model,
53
  # dummy_input=(torch.tensor([10]), torch.tensor([5])),
54
+ dummy_input=torch.from_numpy(inputset[0]),
 
 
 
55
  )
56
 
57
  print("get proxy function ...")
 
59
  # This is done in order to be able to provide any modules with arbitrary numbers of
60
  # encrypted arguments to Concrete Numpy's compiler
61
  numpy_filter_proxy, parameters_mapping = generate_proxy_function(
62
+ numpy_module.numpy_forward, ["inputs"]
63
  )
64
 
65
  print("Compile the filter and retrieve its FHE circuit ...")
66
  compiler = Compiler(
67
  numpy_filter_proxy,
68
  {
69
+ parameters_mapping["inputs"]: "encrypted",
 
70
  },
71
  )
72
  self.fhe_circuit = compiler.compile(inputset)
matchers/filter blur/deployment/circuit.mlir DELETED
@@ -1,11 +0,0 @@
1
- module {
2
- func.func @main(%arg0: tensor<100x100x3x!FHE.eint<12>>) -> tensor<98x98x3x!FHE.eint<12>> {
3
- %0 = "FHELinalg.transpose"(%arg0) {axes = [2, 1, 0]} : (tensor<100x100x3x!FHE.eint<12>>) -> tensor<3x100x100x!FHE.eint<12>>
4
- %expanded = tensor.expand_shape %0 [[0, 1], [2], [3]] : tensor<3x100x100x!FHE.eint<12>> into tensor<1x3x100x100x!FHE.eint<12>>
5
- %cst = arith.constant dense<1> : tensor<3x1x3x3xi13>
6
- %1 = "FHELinalg.conv2d"(%expanded, %cst) {dilations = dense<1> : tensor<2xi64>, group = 3 : i64, padding = dense<0> : tensor<4xi64>, strides = dense<1> : tensor<2xi64>} : (tensor<1x3x100x100x!FHE.eint<12>>, tensor<3x1x3x3xi13>) -> tensor<1x3x98x98x!FHE.eint<12>>
7
- %2 = "FHELinalg.transpose"(%1) {axes = [0, 3, 2, 1]} : (tensor<1x3x98x98x!FHE.eint<12>>) -> tensor<1x98x98x3x!FHE.eint<12>>
8
- %collapsed = tensor.collapse_shape %2 [[0, 1], [2], [3]] : tensor<1x98x98x3x!FHE.eint<12>> into tensor<98x98x3x!FHE.eint<12>>
9
- return %collapsed : tensor<98x98x3x!FHE.eint<12>>
10
- }
11
- }
 
 
 
 
 
 
 
 
 
 
 
 
matchers/filter blur/deployment/configuration.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "verbose": false,
3
- "show_graph": null,
4
- "show_mlir": null,
5
- "show_optimizer": null,
6
- "dump_artifacts_on_unexpected_failures": true,
7
- "enable_unsafe_features": false,
8
- "use_insecure_key_cache": false,
9
- "insecure_key_cache_location": null,
10
- "loop_parallelize": true,
11
- "dataflow_parallelize": false,
12
- "auto_parallelize": false,
13
- "jit": false,
14
- "p_error": null,
15
- "global_p_error": null,
16
- "auto_adjust_rounders": false,
17
- "single_precision": true,
18
- "parameter_selection_strategy": "mono",
19
- "show_progress": false,
20
- "progress_title": "",
21
- "progress_tag": false
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,3 +1,6 @@
1
- concrete-ml==1.6.1
2
  gradio
3
- more_itertools
 
 
 
 
1
+ concrete-ml==1.6.0
2
  gradio
3
+ more_itertools
4
+ torchvision==0.14.1
5
+ facenet-pytorch==2.5.3
6
+ opencv-python==4.10.0.84
server.py CHANGED
@@ -5,35 +5,22 @@ from typing import List
5
  from fastapi import FastAPI, File, Form, UploadFile
6
  from fastapi.responses import JSONResponse, Response
7
 
8
- from common import (
9
- ENCRYPTED_OUTPUT_NAME,
10
- ENCRYPTED_QUERY_NAME,
11
- ENCRYPTED_REFERENCE_NAME,
12
- MATCHERS_PATH,
13
- SERVER_TMP_PATH,
14
- AVAILABLE_MATCHERS,
15
- )
16
- from client_server_interface import FHEServer
17
-
18
- # Load the server objects related to all currently available matchers once and for all
19
- FHE_SERVERS = {
20
- matcher: FHEServer(MATCHERS_PATH / f"{matcher}/deployment")
21
- for matcher in AVAILABLE_MATCHERS
22
- }
23
-
24
-
25
- def get_server_file_path(name, user_id, matcher_name):
26
  """Get the correct temporary file path for the server.
27
 
28
  Args:
29
  name (str): The desired file name.
30
  user_id (int): The current user's ID.
31
- matcher_name (str): The matcher chosen by the user
32
 
33
  Returns:
34
  pathlib.Path: The file path.
35
  """
36
- return SERVER_TMP_PATH / f"{name}_{matcher_name}_{user_id}"
37
 
38
 
39
  # Initialize an instance of FastAPI
@@ -43,83 +30,57 @@ app = FastAPI()
43
  # Define the default route
44
  @app.get("/")
45
  def root():
46
- return {"message": "Welcome to Your biometric image matching!"}
47
 
48
 
49
  @app.post("/send_input")
50
  def send_input(
51
  user_id: str = Form(),
52
- matcher: str = Form(),
53
  files: List[UploadFile] = File(),
54
  ):
55
  """Send the inputs to the server."""
56
  # Retrieve the encrypted input image and the evaluation key paths
57
- encrypted_query_image_path = get_server_file_path(
58
- ENCRYPTED_QUERY_NAME, user_id, matcher
59
- )
60
- encrypted_reference_image_path = get_server_file_path(
61
- ENCRYPTED_REFERENCE_NAME, user_id, matcher
62
- )
63
- evaluation_key_path = get_server_file_path("evaluation_key", user_id, matcher)
64
 
65
  # Write the files using the above paths
66
- with encrypted_query_image_path.open(
67
- "wb"
68
- ) as encrypted_query_image_file, encrypted_reference_image_path.open(
69
  "wb"
70
- ) as encrypted_reference_image_file, evaluation_key_path.open(
71
- "wb"
72
- ) as evaluation_key:
73
- encrypted_query_image_file.write(files[0].file.read())
74
- encrypted_reference_image_file.write(files[1].file.read())
75
- evaluation_key.write(files[2].file.read())
76
 
77
 
78
  @app.post("/run_fhe")
79
  def run_fhe(
80
  user_id: str = Form(),
81
- matcher: str = Form(),
82
  ):
83
- """Execute the matcher on the encrypted input images using FHE."""
84
  # Retrieve the encrypted input image and the evaluation key paths
85
- encrypted_query_image_path = get_server_file_path(
86
- "encrypted_query_image", user_id, matcher
87
- )
88
- encrypted_reference_image_path = get_server_file_path(
89
- "encrypted_reference_image", user_id, matcher
90
- )
91
- evaluation_key_path = get_server_file_path("evaluation_key", user_id, matcher)
92
 
93
  # Read the files using the above paths
94
- with encrypted_query_image_path.open(
95
- "rb"
96
- ) as encrypted_query_image_file, encrypted_reference_image_path.open(
97
- "rb"
98
- ) as encrypted_reference_image_file, evaluation_key_path.open(
99
  "rb"
100
- ) as evaluation_key_file:
101
- encrypted_query_image = encrypted_query_image_file.read()
102
- encrypted_reference_image = encrypted_reference_image_file.read()
103
  evaluation_key = evaluation_key_file.read()
104
 
105
- # Load the FHE server related to the chosen matcher
106
- fhe_server = FHE_SERVERS[matcher]
107
 
108
  # Run the FHE execution
109
  start = time.time()
110
- encrypted_output = fhe_server.run(
111
- encrypted_query_image, encrypted_reference_image, evaluation_key
112
- )
113
  fhe_execution_time = round(time.time() - start, 2)
114
 
115
- # Retrieve the encrypted output path
116
- encrypted_output_path = get_server_file_path(
117
- ENCRYPTED_OUTPUT_NAME, user_id, matcher
118
- )
119
 
120
  # Write the file using the above path
121
  with encrypted_output_path.open("wb") as encrypted_output:
122
- encrypted_output.write(encrypted_output)
123
 
124
  return JSONResponse(content=fhe_execution_time)
125
 
@@ -127,13 +88,10 @@ def run_fhe(
127
  @app.post("/get_output")
128
  def get_output(
129
  user_id: str = Form(),
130
- matcher: str = Form(),
131
  ):
132
- """Retrieve the encrypted output."""
133
- # Retrieve the encrypted output path
134
- encrypted_output_path = get_server_file_path(
135
- ENCRYPTED_OUTPUT_NAME, user_id, matcher
136
- )
137
 
138
  # Read the file using the above path
139
  with encrypted_output_path.open("rb") as encrypted_output_file:
 
5
  from fastapi import FastAPI, File, Form, UploadFile
6
  from fastapi.responses import JSONResponse, Response
7
 
8
+ from common import SERVER_TMP_PATH
9
+ from concrete.ml.deployment import FHEModelClient, FHEModelServer
10
+
11
+
12
+ def get_server_file_path(name, user_id):
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  """Get the correct temporary file path for the server.
14
 
15
  Args:
16
  name (str): The desired file name.
17
  user_id (int): The current user's ID.
18
+ filter_name (str): The filter chosen by the user
19
 
20
  Returns:
21
  pathlib.Path: The file path.
22
  """
23
+ return SERVER_TMP_PATH / f"{name}_{user_id}"
24
 
25
 
26
  # Initialize an instance of FastAPI
 
30
  # Define the default route
31
  @app.get("/")
32
  def root():
33
+ return {"message": "Welcome to Your Image FHE Filter Server!"}
34
 
35
 
36
  @app.post("/send_input")
37
  def send_input(
38
  user_id: str = Form(),
 
39
  files: List[UploadFile] = File(),
40
  ):
41
  """Send the inputs to the server."""
42
  # Retrieve the encrypted input image and the evaluation key paths
43
+ encrypted_embedding_path = get_server_file_path("encrypted_embedding", user_id)
44
+ evaluation_key_path = get_server_file_path("evaluation_key", user_id)
 
 
 
 
 
45
 
46
  # Write the files using the above paths
47
+ with encrypted_embedding_path.open(
 
 
48
  "wb"
49
+ ) as encrypted_embedding, evaluation_key_path.open("wb") as evaluation_key:
50
+ encrypted_embedding.write(files[0].file.read())
51
+ evaluation_key.write(files[1].file.read())
 
 
 
52
 
53
 
54
  @app.post("/run_fhe")
55
  def run_fhe(
56
  user_id: str = Form(),
 
57
  ):
58
+ """Execute the filter on the encrypted input image using FHE."""
59
  # Retrieve the encrypted input image and the evaluation key paths
60
+ encrypted_image_path = get_server_file_path("encrypted_embedding", user_id)
61
+ evaluation_key_path = get_server_file_path("evaluation_key", user_id)
 
 
 
 
 
62
 
63
  # Read the files using the above paths
64
+ with encrypted_image_path.open(
 
 
 
 
65
  "rb"
66
+ ) as encrypted_image_file, evaluation_key_path.open("rb") as evaluation_key_file:
67
+ encrypted_image = encrypted_image_file.read()
 
68
  evaluation_key = evaluation_key_file.read()
69
 
70
+ # Load the FHE server related to the chosen filter
71
+ fhe_server = FHEModelServer("client_server")
72
 
73
  # Run the FHE execution
74
  start = time.time()
75
+ encrypted_output_image = fhe_server.run(encrypted_image, evaluation_key)
 
 
76
  fhe_execution_time = round(time.time() - start, 2)
77
 
78
+ # Retrieve the encrypted output image path
79
+ encrypted_output_path = get_server_file_path("encrypted_output", user_id)
 
 
80
 
81
  # Write the file using the above path
82
  with encrypted_output_path.open("wb") as encrypted_output:
83
+ encrypted_output.write(encrypted_output_image)
84
 
85
  return JSONResponse(content=fhe_execution_time)
86
 
 
88
  @app.post("/get_output")
89
  def get_output(
90
  user_id: str = Form(),
 
91
  ):
92
+ """Retrieve the encrypted output image."""
93
+ # Retrieve the encrypted output image path
94
+ encrypted_output_path = get_server_file_path("encrypted_output", user_id)
 
 
95
 
96
  # Read the file using the above path
97
  with encrypted_output_path.open("rb") as encrypted_output_file: