Roman
commited on
chore: make the client-server interface inherit from CML
Browse files- app.py +47 -44
- compile.py +7 -6
- custom_client_server.py +19 -188
- filters.py +37 -82
- filters/black and white/deployment/client.zip +1 -1
- filters/black and white/deployment/serialized_processing.json +1 -1
- filters/black and white/deployment/server.zip +1 -1
- filters/black_and_white/deployment/client.zip +0 -3
- filters/black_and_white/deployment/serialized_processing.json +0 -1
- filters/black_and_white/deployment/server.zip +0 -3
- filters/black_and_white/server.onnx +0 -3
- filters/blur/deployment/client.zip +1 -1
- filters/blur/deployment/serialized_processing.json +1 -1
- filters/blur/deployment/server.zip +1 -1
- filters/identity/deployment/client.zip +1 -1
- filters/identity/deployment/serialized_processing.json +1 -1
- filters/identity/deployment/server.zip +1 -1
- filters/inverted/deployment/client.zip +1 -1
- filters/inverted/deployment/serialized_processing.json +1 -1
- filters/inverted/deployment/server.zip +1 -1
- filters/ridge detection/deployment/client.zip +1 -1
- filters/ridge detection/deployment/serialized_processing.json +1 -1
- filters/ridge detection/deployment/server.zip +1 -1
- filters/ridge_detection/deployment/client.zip +0 -3
- filters/ridge_detection/deployment/serialized_processing.json +0 -1
- filters/ridge_detection/deployment/server.zip +0 -3
- filters/ridge_detection/server.onnx +0 -3
- filters/rotate/deployment/client.zip +1 -1
- filters/rotate/deployment/serialized_processing.json +1 -1
- filters/rotate/deployment/server.zip +1 -1
- filters/sharpen/deployment/client.zip +1 -1
- filters/sharpen/deployment/serialized_processing.json +1 -1
- filters/sharpen/deployment/server.zip +1 -1
- generate_dev_files.py +6 -6
- server.py +5 -5
app.py
CHANGED
|
@@ -26,18 +26,18 @@ subprocess.Popen(["uvicorn", "server:app"], cwd=REPO_DIR)
|
|
| 26 |
time.sleep(3)
|
| 27 |
|
| 28 |
|
| 29 |
-
def decrypt_output_with_wrong_key(encrypted_image,
|
| 30 |
"""Decrypt the encrypted output using a different private key.
|
| 31 |
"""
|
| 32 |
# Retrieve the filter's deployment path
|
| 33 |
-
filter_path = FILTERS_PATH / f"{
|
| 34 |
|
| 35 |
# Instantiate the client interface and generate a new private key
|
| 36 |
wrong_client = CustomFHEClient(filter_path, WRONG_KEYS_PATH)
|
| 37 |
wrong_client.generate_private_and_evaluation_keys(force=True)
|
| 38 |
|
| 39 |
# Deserialize, decrypt and post-processing the encrypted output using the new private key
|
| 40 |
-
output_image = wrong_client.
|
| 41 |
|
| 42 |
return output_image
|
| 43 |
|
|
@@ -61,33 +61,33 @@ def shorten_bytes_object(bytes_object, limit=500):
|
|
| 61 |
return bytes_object[shift : limit + shift].hex()
|
| 62 |
|
| 63 |
|
| 64 |
-
def get_client(user_id,
|
| 65 |
"""Get the client API.
|
| 66 |
|
| 67 |
Args:
|
| 68 |
user_id (int): The current user's ID.
|
| 69 |
-
|
| 70 |
|
| 71 |
Returns:
|
| 72 |
CustomFHEClient: The client API.
|
| 73 |
"""
|
| 74 |
return CustomFHEClient(
|
| 75 |
-
FILTERS_PATH / f"{
|
| 76 |
)
|
| 77 |
|
| 78 |
|
| 79 |
-
def get_client_file_path(name, user_id,
|
| 80 |
"""Get the correct temporary file path for the client.
|
| 81 |
|
| 82 |
Args:
|
| 83 |
name (str): The desired file name.
|
| 84 |
user_id (int): The current user's ID.
|
| 85 |
-
|
| 86 |
|
| 87 |
Returns:
|
| 88 |
pathlib.Path: The file path.
|
| 89 |
"""
|
| 90 |
-
return CLIENT_TMP_PATH / f"{name}_{
|
| 91 |
|
| 92 |
|
| 93 |
def clean_temporary_files(n_keys=20):
|
|
@@ -121,11 +121,11 @@ def clean_temporary_files(n_keys=20):
|
|
| 121 |
file.unlink()
|
| 122 |
|
| 123 |
|
| 124 |
-
def keygen(
|
| 125 |
"""Generate the private key associated to a filter.
|
| 126 |
|
| 127 |
Args:
|
| 128 |
-
|
| 129 |
|
| 130 |
Returns:
|
| 131 |
(user_id, True) (Tuple[int, bool]): The current user's ID and a boolean used for visual display.
|
|
@@ -138,7 +138,7 @@ def keygen(image_filter):
|
|
| 138 |
user_id = numpy.random.randint(0, 2**32)
|
| 139 |
|
| 140 |
# Retrieve the client API
|
| 141 |
-
client = get_client(user_id,
|
| 142 |
|
| 143 |
# Generate a private key
|
| 144 |
client.generate_private_and_evaluation_keys(force=True)
|
|
@@ -154,7 +154,7 @@ def keygen(image_filter):
|
|
| 154 |
|
| 155 |
# Save evaluation_key as bytes in a file as it is too large to pass through regular Gradio
|
| 156 |
# buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
| 157 |
-
evaluation_key_path = get_client_file_path("evaluation_key", user_id,
|
| 158 |
|
| 159 |
with evaluation_key_path.open("wb") as evaluation_key_file:
|
| 160 |
evaluation_key_file.write(evaluation_key)
|
|
@@ -162,13 +162,13 @@ def keygen(image_filter):
|
|
| 162 |
return (user_id, True, private_key_size)
|
| 163 |
|
| 164 |
|
| 165 |
-
def encrypt(user_id, input_image,
|
| 166 |
"""Encrypt the given image for a specific user and filter.
|
| 167 |
|
| 168 |
Args:
|
| 169 |
user_id (int): The current user's ID.
|
| 170 |
input_image (numpy.ndarray): The image to encrypt.
|
| 171 |
-
|
| 172 |
|
| 173 |
Returns:
|
| 174 |
(input_image, encrypted_image_short) (Tuple[bytes]): The encrypted image and one of its
|
|
@@ -182,17 +182,20 @@ def encrypt(user_id, input_image, image_filter):
|
|
| 182 |
raise gr.Error("Please choose an image first.")
|
| 183 |
|
| 184 |
# Retrieve the client API
|
| 185 |
-
client = get_client(user_id,
|
| 186 |
|
| 187 |
-
# Pre-process
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
# Compute the input's size in Megabytes
|
| 191 |
encrypted_input_size = len(encrypted_image) / 1000000
|
| 192 |
|
| 193 |
# Save encrypted_image to bytes in a file, since too large to pass through regular Gradio
|
| 194 |
# buttons, https://github.com/gradio-app/gradio/issues/1877
|
| 195 |
-
encrypted_image_path = get_client_file_path("encrypted_image", user_id,
|
| 196 |
|
| 197 |
with encrypted_image_path.open("wb") as encrypted_image_file:
|
| 198 |
encrypted_image_file.write(encrypted_image)
|
|
@@ -203,20 +206,20 @@ def encrypt(user_id, input_image, image_filter):
|
|
| 203 |
return (input_image, encrypted_image_short, encrypted_input_size)
|
| 204 |
|
| 205 |
|
| 206 |
-
def send_input(user_id,
|
| 207 |
"""Send the encrypted input image as well as the evaluation key to the server.
|
| 208 |
|
| 209 |
Args:
|
| 210 |
user_id (int): The current user's ID.
|
| 211 |
-
|
| 212 |
"""
|
| 213 |
# Get the evaluation key path
|
| 214 |
-
evaluation_key_path = get_client_file_path("evaluation_key", user_id,
|
| 215 |
|
| 216 |
if user_id == "" or not evaluation_key_path.is_file():
|
| 217 |
raise gr.Error("Please generate the private key first.")
|
| 218 |
|
| 219 |
-
encrypted_input_path = get_client_file_path("encrypted_image", user_id,
|
| 220 |
|
| 221 |
if not encrypted_input_path.is_file():
|
| 222 |
raise gr.Error("Please generate the private key and then encrypt an image first.")
|
|
@@ -224,7 +227,7 @@ def send_input(user_id, image_filter):
|
|
| 224 |
# Define the data and files to post
|
| 225 |
data = {
|
| 226 |
"user_id": user_id,
|
| 227 |
-
"filter":
|
| 228 |
}
|
| 229 |
|
| 230 |
files = [
|
|
@@ -242,16 +245,16 @@ def send_input(user_id, image_filter):
|
|
| 242 |
return response.ok
|
| 243 |
|
| 244 |
|
| 245 |
-
def run_fhe(user_id,
|
| 246 |
"""Apply the filter on the encrypted image previously sent using FHE.
|
| 247 |
|
| 248 |
Args:
|
| 249 |
user_id (int): The current user's ID.
|
| 250 |
-
|
| 251 |
"""
|
| 252 |
data = {
|
| 253 |
"user_id": user_id,
|
| 254 |
-
"filter":
|
| 255 |
}
|
| 256 |
|
| 257 |
# Trigger the FHE execution on the encrypted image previously sent
|
|
@@ -266,12 +269,12 @@ def run_fhe(user_id, image_filter):
|
|
| 266 |
raise gr.Error("Please wait for the input image to be sent to the server.")
|
| 267 |
|
| 268 |
|
| 269 |
-
def get_output(user_id,
|
| 270 |
"""Retrieve the encrypted output image.
|
| 271 |
|
| 272 |
Args:
|
| 273 |
user_id (int): The current user's ID.
|
| 274 |
-
|
| 275 |
|
| 276 |
Returns:
|
| 277 |
encrypted_output_image_short (bytes): A representation of the encrypted result.
|
|
@@ -279,7 +282,7 @@ def get_output(user_id, image_filter):
|
|
| 279 |
"""
|
| 280 |
data = {
|
| 281 |
"user_id": user_id,
|
| 282 |
-
"filter":
|
| 283 |
}
|
| 284 |
|
| 285 |
# Retrieve the encrypted output image
|
|
@@ -296,25 +299,25 @@ def get_output(user_id, image_filter):
|
|
| 296 |
|
| 297 |
# Save the encrypted output to bytes in a file as it is too large to pass through regular
|
| 298 |
# Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
| 299 |
-
encrypted_output_path = get_client_file_path("encrypted_output", user_id,
|
| 300 |
|
| 301 |
with encrypted_output_path.open("wb") as encrypted_output_file:
|
| 302 |
encrypted_output_file.write(encrypted_output)
|
| 303 |
|
| 304 |
# Decrypt the image using a different (wrong) key for display
|
| 305 |
-
output_image_representation = decrypt_output_with_wrong_key(encrypted_output,
|
| 306 |
|
| 307 |
return output_image_representation, encrypted_output_size
|
| 308 |
else:
|
| 309 |
raise gr.Error("Please wait for the FHE execution to be completed.")
|
| 310 |
|
| 311 |
|
| 312 |
-
def decrypt_output(user_id,
|
| 313 |
"""Decrypt the result.
|
| 314 |
|
| 315 |
Args:
|
| 316 |
user_id (int): The current user's ID.
|
| 317 |
-
|
| 318 |
|
| 319 |
Returns:
|
| 320 |
(output_image, False, False) ((Tuple[numpy.ndarray, bool, bool]): The decrypted output, as
|
|
@@ -325,7 +328,7 @@ def decrypt_output(user_id, image_filter):
|
|
| 325 |
raise gr.Error("Please generate the private key first.")
|
| 326 |
|
| 327 |
# Get the encrypted output path
|
| 328 |
-
encrypted_output_path = get_client_file_path("encrypted_output", user_id,
|
| 329 |
|
| 330 |
if not encrypted_output_path.is_file():
|
| 331 |
raise gr.Error("Please run the FHE execution first.")
|
|
@@ -335,10 +338,10 @@ def decrypt_output(user_id, image_filter):
|
|
| 335 |
encrypted_output_image = encrypted_output_file.read()
|
| 336 |
|
| 337 |
# Retrieve the client API
|
| 338 |
-
client = get_client(user_id,
|
| 339 |
|
| 340 |
# Deserialize, decrypt and post-process the encrypted output
|
| 341 |
-
output_image = client.
|
| 342 |
|
| 343 |
return output_image, False, False
|
| 344 |
|
|
@@ -374,7 +377,7 @@ with demo:
|
|
| 374 |
)
|
| 375 |
|
| 376 |
gr.Markdown("### Step 2. Choose your filter")
|
| 377 |
-
|
| 378 |
choices=AVAILABLE_FILTERS, value="inverted", label="Choose your filter", interactive=True
|
| 379 |
)
|
| 380 |
|
|
@@ -475,36 +478,36 @@ with demo:
|
|
| 475 |
# Button to generate the private key
|
| 476 |
keygen_button.click(
|
| 477 |
keygen,
|
| 478 |
-
inputs=[
|
| 479 |
outputs=[user_id, keygen_checkbox, private_key_size],
|
| 480 |
)
|
| 481 |
|
| 482 |
# Button to encrypt inputs on the client side
|
| 483 |
encrypt_button.click(
|
| 484 |
encrypt,
|
| 485 |
-
inputs=[user_id, input_image,
|
| 486 |
outputs=[original_image, encrypted_input, encrypted_input_size],
|
| 487 |
)
|
| 488 |
|
| 489 |
# Button to send the encodings to the server using post method
|
| 490 |
send_input_button.click(
|
| 491 |
-
send_input, inputs=[user_id,
|
| 492 |
)
|
| 493 |
|
| 494 |
# Button to send the encodings to the server using post method
|
| 495 |
-
execute_fhe_button.click(run_fhe, inputs=[user_id,
|
| 496 |
|
| 497 |
# Button to send the encodings to the server using post method
|
| 498 |
get_output_button.click(
|
| 499 |
get_output,
|
| 500 |
-
inputs=[user_id,
|
| 501 |
outputs=[encrypted_output_representation, encrypted_output_size]
|
| 502 |
)
|
| 503 |
|
| 504 |
# Button to decrypt the output on the client side
|
| 505 |
decrypt_button.click(
|
| 506 |
decrypt_output,
|
| 507 |
-
inputs=[user_id,
|
| 508 |
outputs=[output_image, keygen_checkbox, send_input_checkbox],
|
| 509 |
)
|
| 510 |
|
|
|
|
| 26 |
time.sleep(3)
|
| 27 |
|
| 28 |
|
| 29 |
+
def decrypt_output_with_wrong_key(encrypted_image, filter_name):
|
| 30 |
"""Decrypt the encrypted output using a different private key.
|
| 31 |
"""
|
| 32 |
# Retrieve the filter's deployment path
|
| 33 |
+
filter_path = FILTERS_PATH / f"{filter_name}/deployment"
|
| 34 |
|
| 35 |
# Instantiate the client interface and generate a new private key
|
| 36 |
wrong_client = CustomFHEClient(filter_path, WRONG_KEYS_PATH)
|
| 37 |
wrong_client.generate_private_and_evaluation_keys(force=True)
|
| 38 |
|
| 39 |
# Deserialize, decrypt and post-processing the encrypted output using the new private key
|
| 40 |
+
output_image = wrong_client.deserialize_decrypt_dequantize(encrypted_image)
|
| 41 |
|
| 42 |
return output_image
|
| 43 |
|
|
|
|
| 61 |
return bytes_object[shift : limit + shift].hex()
|
| 62 |
|
| 63 |
|
| 64 |
+
def get_client(user_id, filter_name):
|
| 65 |
"""Get the client API.
|
| 66 |
|
| 67 |
Args:
|
| 68 |
user_id (int): The current user's ID.
|
| 69 |
+
filter_name (str): The filter chosen by the user
|
| 70 |
|
| 71 |
Returns:
|
| 72 |
CustomFHEClient: The client API.
|
| 73 |
"""
|
| 74 |
return CustomFHEClient(
|
| 75 |
+
FILTERS_PATH / f"{filter_name}/deployment", KEYS_PATH / f"{filter_name}_{user_id}"
|
| 76 |
)
|
| 77 |
|
| 78 |
|
| 79 |
+
def get_client_file_path(name, user_id, filter_name):
|
| 80 |
"""Get the correct temporary file path for the client.
|
| 81 |
|
| 82 |
Args:
|
| 83 |
name (str): The desired file name.
|
| 84 |
user_id (int): The current user's ID.
|
| 85 |
+
filter_name (str): The filter chosen by the user
|
| 86 |
|
| 87 |
Returns:
|
| 88 |
pathlib.Path: The file path.
|
| 89 |
"""
|
| 90 |
+
return CLIENT_TMP_PATH / f"{name}_{filter_name}_{user_id}"
|
| 91 |
|
| 92 |
|
| 93 |
def clean_temporary_files(n_keys=20):
|
|
|
|
| 121 |
file.unlink()
|
| 122 |
|
| 123 |
|
| 124 |
+
def keygen(filter_name):
|
| 125 |
"""Generate the private key associated to a filter.
|
| 126 |
|
| 127 |
Args:
|
| 128 |
+
filter_name (str): The current filter to consider.
|
| 129 |
|
| 130 |
Returns:
|
| 131 |
(user_id, True) (Tuple[int, bool]): The current user's ID and a boolean used for visual display.
|
|
|
|
| 138 |
user_id = numpy.random.randint(0, 2**32)
|
| 139 |
|
| 140 |
# Retrieve the client API
|
| 141 |
+
client = get_client(user_id, filter_name)
|
| 142 |
|
| 143 |
# Generate a private key
|
| 144 |
client.generate_private_and_evaluation_keys(force=True)
|
|
|
|
| 154 |
|
| 155 |
# Save evaluation_key as bytes in a file as it is too large to pass through regular Gradio
|
| 156 |
# buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
| 157 |
+
evaluation_key_path = get_client_file_path("evaluation_key", user_id, filter_name)
|
| 158 |
|
| 159 |
with evaluation_key_path.open("wb") as evaluation_key_file:
|
| 160 |
evaluation_key_file.write(evaluation_key)
|
|
|
|
| 162 |
return (user_id, True, private_key_size)
|
| 163 |
|
| 164 |
|
| 165 |
+
def encrypt(user_id, input_image, filter_name):
|
| 166 |
"""Encrypt the given image for a specific user and filter.
|
| 167 |
|
| 168 |
Args:
|
| 169 |
user_id (int): The current user's ID.
|
| 170 |
input_image (numpy.ndarray): The image to encrypt.
|
| 171 |
+
filter_name (str): The current filter to consider.
|
| 172 |
|
| 173 |
Returns:
|
| 174 |
(input_image, encrypted_image_short) (Tuple[bytes]): The encrypted image and one of its
|
|
|
|
| 182 |
raise gr.Error("Please choose an image first.")
|
| 183 |
|
| 184 |
# Retrieve the client API
|
| 185 |
+
client = get_client(user_id, filter_name)
|
| 186 |
|
| 187 |
+
# Pre-process the input image as Torch and Numpy don't follow the same shape format
|
| 188 |
+
preprocessed_input_image = client.model.pre_processing(input_image)
|
| 189 |
+
|
| 190 |
+
# Encrypt and serialize the image
|
| 191 |
+
encrypted_image = client.quantize_encrypt_serialize(preprocessed_input_image)
|
| 192 |
|
| 193 |
# Compute the input's size in Megabytes
|
| 194 |
encrypted_input_size = len(encrypted_image) / 1000000
|
| 195 |
|
| 196 |
# Save encrypted_image to bytes in a file, since too large to pass through regular Gradio
|
| 197 |
# buttons, https://github.com/gradio-app/gradio/issues/1877
|
| 198 |
+
encrypted_image_path = get_client_file_path("encrypted_image", user_id, filter_name)
|
| 199 |
|
| 200 |
with encrypted_image_path.open("wb") as encrypted_image_file:
|
| 201 |
encrypted_image_file.write(encrypted_image)
|
|
|
|
| 206 |
return (input_image, encrypted_image_short, encrypted_input_size)
|
| 207 |
|
| 208 |
|
| 209 |
+
def send_input(user_id, filter_name):
|
| 210 |
"""Send the encrypted input image as well as the evaluation key to the server.
|
| 211 |
|
| 212 |
Args:
|
| 213 |
user_id (int): The current user's ID.
|
| 214 |
+
filter_name (str): The current filter to consider.
|
| 215 |
"""
|
| 216 |
# Get the evaluation key path
|
| 217 |
+
evaluation_key_path = get_client_file_path("evaluation_key", user_id, filter_name)
|
| 218 |
|
| 219 |
if user_id == "" or not evaluation_key_path.is_file():
|
| 220 |
raise gr.Error("Please generate the private key first.")
|
| 221 |
|
| 222 |
+
encrypted_input_path = get_client_file_path("encrypted_image", user_id, filter_name)
|
| 223 |
|
| 224 |
if not encrypted_input_path.is_file():
|
| 225 |
raise gr.Error("Please generate the private key and then encrypt an image first.")
|
|
|
|
| 227 |
# Define the data and files to post
|
| 228 |
data = {
|
| 229 |
"user_id": user_id,
|
| 230 |
+
"filter": filter_name,
|
| 231 |
}
|
| 232 |
|
| 233 |
files = [
|
|
|
|
| 245 |
return response.ok
|
| 246 |
|
| 247 |
|
| 248 |
+
def run_fhe(user_id, filter_name):
|
| 249 |
"""Apply the filter on the encrypted image previously sent using FHE.
|
| 250 |
|
| 251 |
Args:
|
| 252 |
user_id (int): The current user's ID.
|
| 253 |
+
filter_name (str): The current filter to consider.
|
| 254 |
"""
|
| 255 |
data = {
|
| 256 |
"user_id": user_id,
|
| 257 |
+
"filter": filter_name,
|
| 258 |
}
|
| 259 |
|
| 260 |
# Trigger the FHE execution on the encrypted image previously sent
|
|
|
|
| 269 |
raise gr.Error("Please wait for the input image to be sent to the server.")
|
| 270 |
|
| 271 |
|
| 272 |
+
def get_output(user_id, filter_name):
|
| 273 |
"""Retrieve the encrypted output image.
|
| 274 |
|
| 275 |
Args:
|
| 276 |
user_id (int): The current user's ID.
|
| 277 |
+
filter_name (str): The current filter to consider.
|
| 278 |
|
| 279 |
Returns:
|
| 280 |
encrypted_output_image_short (bytes): A representation of the encrypted result.
|
|
|
|
| 282 |
"""
|
| 283 |
data = {
|
| 284 |
"user_id": user_id,
|
| 285 |
+
"filter": filter_name,
|
| 286 |
}
|
| 287 |
|
| 288 |
# Retrieve the encrypted output image
|
|
|
|
| 299 |
|
| 300 |
# Save the encrypted output to bytes in a file as it is too large to pass through regular
|
| 301 |
# Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
| 302 |
+
encrypted_output_path = get_client_file_path("encrypted_output", user_id, filter_name)
|
| 303 |
|
| 304 |
with encrypted_output_path.open("wb") as encrypted_output_file:
|
| 305 |
encrypted_output_file.write(encrypted_output)
|
| 306 |
|
| 307 |
# Decrypt the image using a different (wrong) key for display
|
| 308 |
+
output_image_representation = decrypt_output_with_wrong_key(encrypted_output, filter_name)
|
| 309 |
|
| 310 |
return output_image_representation, encrypted_output_size
|
| 311 |
else:
|
| 312 |
raise gr.Error("Please wait for the FHE execution to be completed.")
|
| 313 |
|
| 314 |
|
| 315 |
+
def decrypt_output(user_id, filter_name):
|
| 316 |
"""Decrypt the result.
|
| 317 |
|
| 318 |
Args:
|
| 319 |
user_id (int): The current user's ID.
|
| 320 |
+
filter_name (str): The current filter to consider.
|
| 321 |
|
| 322 |
Returns:
|
| 323 |
(output_image, False, False) ((Tuple[numpy.ndarray, bool, bool]): The decrypted output, as
|
|
|
|
| 328 |
raise gr.Error("Please generate the private key first.")
|
| 329 |
|
| 330 |
# Get the encrypted output path
|
| 331 |
+
encrypted_output_path = get_client_file_path("encrypted_output", user_id, filter_name)
|
| 332 |
|
| 333 |
if not encrypted_output_path.is_file():
|
| 334 |
raise gr.Error("Please run the FHE execution first.")
|
|
|
|
| 338 |
encrypted_output_image = encrypted_output_file.read()
|
| 339 |
|
| 340 |
# Retrieve the client API
|
| 341 |
+
client = get_client(user_id, filter_name)
|
| 342 |
|
| 343 |
# Deserialize, decrypt and post-process the encrypted output
|
| 344 |
+
output_image = client.deserialize_decrypt_dequantize(encrypted_output_image)
|
| 345 |
|
| 346 |
return output_image, False, False
|
| 347 |
|
|
|
|
| 377 |
)
|
| 378 |
|
| 379 |
gr.Markdown("### Step 2. Choose your filter")
|
| 380 |
+
filter_name = gr.Dropdown(
|
| 381 |
choices=AVAILABLE_FILTERS, value="inverted", label="Choose your filter", interactive=True
|
| 382 |
)
|
| 383 |
|
|
|
|
| 478 |
# Button to generate the private key
|
| 479 |
keygen_button.click(
|
| 480 |
keygen,
|
| 481 |
+
inputs=[filter_name],
|
| 482 |
outputs=[user_id, keygen_checkbox, private_key_size],
|
| 483 |
)
|
| 484 |
|
| 485 |
# Button to encrypt inputs on the client side
|
| 486 |
encrypt_button.click(
|
| 487 |
encrypt,
|
| 488 |
+
inputs=[user_id, input_image, filter_name],
|
| 489 |
outputs=[original_image, encrypted_input, encrypted_input_size],
|
| 490 |
)
|
| 491 |
|
| 492 |
# Button to send the encodings to the server using post method
|
| 493 |
send_input_button.click(
|
| 494 |
+
send_input, inputs=[user_id, filter_name], outputs=[send_input_checkbox]
|
| 495 |
)
|
| 496 |
|
| 497 |
# Button to send the encodings to the server using post method
|
| 498 |
+
execute_fhe_button.click(run_fhe, inputs=[user_id, filter_name], outputs=[fhe_execution_time])
|
| 499 |
|
| 500 |
# Button to send the encodings to the server using post method
|
| 501 |
get_output_button.click(
|
| 502 |
get_output,
|
| 503 |
+
inputs=[user_id, filter_name],
|
| 504 |
outputs=[encrypted_output_representation, encrypted_output_size]
|
| 505 |
)
|
| 506 |
|
| 507 |
# Button to decrypt the output on the client side
|
| 508 |
decrypt_button.click(
|
| 509 |
decrypt_output,
|
| 510 |
+
inputs=[user_id, filter_name],
|
| 511 |
outputs=[output_image, keygen_checkbox, send_input_checkbox],
|
| 512 |
)
|
| 513 |
|
compile.py
CHANGED
|
@@ -4,21 +4,22 @@ import json
|
|
| 4 |
import shutil
|
| 5 |
import onnx
|
| 6 |
from common import AVAILABLE_FILTERS, FILTERS_PATH, KEYS_PATH
|
| 7 |
-
from custom_client_server import CustomFHEClient
|
|
|
|
| 8 |
|
| 9 |
print("Starting compiling the filters.")
|
| 10 |
|
| 11 |
-
for
|
| 12 |
-
print("\nCompiling filter:",
|
| 13 |
|
| 14 |
# Retrieve the deployment files associated to the current filter
|
| 15 |
-
deployment_path = FILTERS_PATH / f"{
|
| 16 |
|
| 17 |
# Retrieve the client associated to the current filter
|
| 18 |
model = CustomFHEClient(deployment_path, KEYS_PATH).model
|
| 19 |
|
| 20 |
# Load the onnx model
|
| 21 |
-
onnx_model = onnx.load(FILTERS_PATH / f"{
|
| 22 |
|
| 23 |
# Compile the model on a representative inputset, using the loaded onnx model
|
| 24 |
model.compile(onnx_model=onnx_model)
|
|
@@ -34,7 +35,7 @@ for image_filter in AVAILABLE_FILTERS:
|
|
| 34 |
shutil.rmtree(deployment_path)
|
| 35 |
|
| 36 |
# Save the development files needed for deployment
|
| 37 |
-
fhe_dev =
|
| 38 |
fhe_dev.save()
|
| 39 |
|
| 40 |
# Write the serialized_processing.json file in the deployment directory
|
|
|
|
| 4 |
import shutil
|
| 5 |
import onnx
|
| 6 |
from common import AVAILABLE_FILTERS, FILTERS_PATH, KEYS_PATH
|
| 7 |
+
from custom_client_server import CustomFHEClient
|
| 8 |
+
from concrete.ml.deployment import FHEModelDev
|
| 9 |
|
| 10 |
print("Starting compiling the filters.")
|
| 11 |
|
| 12 |
+
for filter_name in AVAILABLE_FILTERS:
|
| 13 |
+
print("\nCompiling filter:", filter_name)
|
| 14 |
|
| 15 |
# Retrieve the deployment files associated to the current filter
|
| 16 |
+
deployment_path = FILTERS_PATH / f"{filter_name}/deployment"
|
| 17 |
|
| 18 |
# Retrieve the client associated to the current filter
|
| 19 |
model = CustomFHEClient(deployment_path, KEYS_PATH).model
|
| 20 |
|
| 21 |
# Load the onnx model
|
| 22 |
+
onnx_model = onnx.load(FILTERS_PATH / f"{filter_name}/server.onnx")
|
| 23 |
|
| 24 |
# Compile the model on a representative inputset, using the loaded onnx model
|
| 25 |
model.compile(onnx_model=onnx_model)
|
|
|
|
| 35 |
shutil.rmtree(deployment_path)
|
| 36 |
|
| 37 |
# Save the development files needed for deployment
|
| 38 |
+
fhe_dev = FHEModelDev(model=model, path_dir=deployment_path)
|
| 39 |
fhe_dev.save()
|
| 40 |
|
| 41 |
# Write the serialized_processing.json file in the deployment directory
|
custom_client_server.py
CHANGED
|
@@ -1,204 +1,35 @@
|
|
| 1 |
-
"Client-server interface implementation for
|
| 2 |
-
|
| 3 |
-
from pathlib import Path
|
| 4 |
-
from typing import Any
|
| 5 |
|
|
|
|
| 6 |
import concrete.numpy as cnp
|
| 7 |
-
import numpy as np
|
| 8 |
from filters import Filter
|
| 9 |
|
| 10 |
-
from concrete.ml.
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
class CustomFHEDev:
|
| 14 |
-
"""Dev API to save the custom integer model, load and run a FHE circuit."""
|
| 15 |
-
|
| 16 |
-
model: Any = None
|
| 17 |
-
|
| 18 |
-
def __init__(self, path_dir: str, model: Any = None):
|
| 19 |
-
"""Initialize the development interface.
|
| 20 |
-
|
| 21 |
-
Args:
|
| 22 |
-
path_dir (str): The path to the directory where the circuit is saved.
|
| 23 |
-
model (Any): The model to use for the development interface.
|
| 24 |
-
"""
|
| 25 |
-
|
| 26 |
-
self.path_dir = Path(path_dir)
|
| 27 |
-
self.model = model
|
| 28 |
|
| 29 |
-
# Create the directory path if it does not exist yet
|
| 30 |
-
Path(self.path_dir).mkdir(parents=True, exist_ok=True)
|
| 31 |
-
|
| 32 |
-
def save(self):
|
| 33 |
-
"""Export all needed artifacts for the client and server.
|
| 34 |
-
|
| 35 |
-
Raises:
|
| 36 |
-
Exception: path_dir is not empty.
|
| 37 |
-
"""
|
| 38 |
-
# Check if the path_dir is empty with pathlib
|
| 39 |
-
listdir = list(Path(self.path_dir).glob("**/*"))
|
| 40 |
-
if len(listdir) > 0:
|
| 41 |
-
raise Exception(
|
| 42 |
-
f"path_dir: {self.path_dir} is not empty."
|
| 43 |
-
"Please delete it before saving a new model."
|
| 44 |
-
)
|
| 45 |
-
|
| 46 |
-
assert_true(
|
| 47 |
-
hasattr(self.model, "fhe_circuit"),
|
| 48 |
-
"The model must be compiled and have a fhe_circuit object",
|
| 49 |
-
)
|
| 50 |
-
|
| 51 |
-
# Model must be compiled with jit=False
|
| 52 |
-
# In a jit model, everything is in memory so it is not serializable.
|
| 53 |
-
assert_true(
|
| 54 |
-
not self.model.fhe_circuit.configuration.jit,
|
| 55 |
-
"The model must be compiled with the configuration option jit=False.",
|
| 56 |
-
)
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
|
| 61 |
-
|
| 62 |
-
path_circuit_server = self.path_dir / "server.zip"
|
| 63 |
-
self.model.fhe_circuit.server.save(path_circuit_server)
|
| 64 |
-
|
| 65 |
-
# Save the circuit for the client
|
| 66 |
-
path_circuit_client = self.path_dir / "client.zip"
|
| 67 |
-
self.model.fhe_circuit.client.save(path_circuit_client)
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
class CustomFHEClient:
|
| 71 |
-
"""Client API to encrypt and decrypt FHE data."""
|
| 72 |
-
|
| 73 |
-
client: cnp.Client
|
| 74 |
-
|
| 75 |
-
def __init__(self, path_dir: str, key_dir: str = None):
|
| 76 |
-
"""Initialize the client interface.
|
| 77 |
-
|
| 78 |
-
Args:
|
| 79 |
-
path_dir (str): The path to the directory where the circuit is saved.
|
| 80 |
-
key_dir (str): The path to the directory where the keys are stored.
|
| 81 |
-
"""
|
| 82 |
-
self.path_dir = Path(path_dir)
|
| 83 |
-
self.key_dir = Path(key_dir)
|
| 84 |
-
|
| 85 |
-
# If path_dir does not exist, raise an error
|
| 86 |
-
assert_true(
|
| 87 |
-
Path(path_dir).exists(), f"{path_dir} does not exist. Please specify a valid path."
|
| 88 |
-
)
|
| 89 |
-
|
| 90 |
-
# Load
|
| 91 |
-
self.load()
|
| 92 |
-
|
| 93 |
-
def load(self): # pylint: disable=no-value-for-parameter
|
| 94 |
"""Load the parameters along with the FHE specs."""
|
| 95 |
|
| 96 |
# Load the client
|
| 97 |
self.client = cnp.Client.load(self.path_dir / "client.zip", self.key_dir)
|
| 98 |
|
| 99 |
-
# Load the
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
def generate_private_and_evaluation_keys(self, force=False):
|
| 103 |
-
"""Generate the private and evaluation keys.
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
def get_serialized_evaluation_keys(self) -> cnp.EvaluationKeys:
|
| 111 |
-
"""Get the serialized evaluation keys.
|
| 112 |
-
|
| 113 |
-
Returns:
|
| 114 |
-
cnp.EvaluationKeys: The evaluation keys.
|
| 115 |
-
"""
|
| 116 |
-
return self.client.evaluation_keys.serialize()
|
| 117 |
-
|
| 118 |
-
def pre_process_encrypt_serialize(self, x: np.ndarray) -> cnp.PublicArguments:
|
| 119 |
-
"""Encrypt and serialize the values.
|
| 120 |
-
|
| 121 |
-
Args:
|
| 122 |
-
x (numpy.ndarray): The values to encrypt and serialize.
|
| 123 |
-
|
| 124 |
-
Returns:
|
| 125 |
-
cnp.PublicArguments: The encrypted and serialized values.
|
| 126 |
-
"""
|
| 127 |
-
# Pre-process the values
|
| 128 |
-
x = self.model.pre_processing(x)
|
| 129 |
-
|
| 130 |
-
# Encrypt the values
|
| 131 |
-
enc_x = self.client.encrypt(x)
|
| 132 |
-
|
| 133 |
-
# Serialize the encrypted values to be sent to the server
|
| 134 |
-
serialized_enc_x = self.client.specs.serialize_public_args(enc_x)
|
| 135 |
-
return serialized_enc_x
|
| 136 |
-
|
| 137 |
-
def deserialize_decrypt_post_process(
|
| 138 |
-
self, serialized_encrypted_output: cnp.PublicArguments
|
| 139 |
-
) -> np.ndarray:
|
| 140 |
-
"""Deserialize, decrypt and post-process the values.
|
| 141 |
-
|
| 142 |
-
Args:
|
| 143 |
-
serialized_encrypted_output (cnp.PublicArguments): The serialized and encrypted output.
|
| 144 |
-
|
| 145 |
-
Returns:
|
| 146 |
-
numpy.ndarray: The decrypted values.
|
| 147 |
-
"""
|
| 148 |
-
# Deserialize the encrypted values
|
| 149 |
-
deserialized_encrypted_output = self.client.specs.unserialize_public_result(
|
| 150 |
-
serialized_encrypted_output
|
| 151 |
)
|
| 152 |
|
| 153 |
-
#
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
# Apply the model post processing
|
| 157 |
-
deserialized_decrypted_output = self.model.post_processing(deserialized_decrypted_output)
|
| 158 |
-
return deserialized_decrypted_output
|
| 159 |
-
|
| 160 |
|
| 161 |
-
|
| 162 |
-
"""Server interface to load and run a FHE circuit."""
|
| 163 |
-
|
| 164 |
-
server: cnp.Server
|
| 165 |
-
|
| 166 |
-
def __init__(self, path_dir: str):
|
| 167 |
-
"""Initialize the server interface.
|
| 168 |
-
|
| 169 |
-
Args:
|
| 170 |
-
path_dir (str): The path to the directory where the circuit is saved.
|
| 171 |
-
"""
|
| 172 |
-
|
| 173 |
-
self.path_dir = Path(path_dir)
|
| 174 |
-
|
| 175 |
-
# Load the FHE circuit
|
| 176 |
-
self.load()
|
| 177 |
-
|
| 178 |
-
def load(self):
|
| 179 |
-
"""Load the circuit."""
|
| 180 |
-
self.server = cnp.Server.load(self.path_dir / "server.zip")
|
| 181 |
-
|
| 182 |
-
def run(
|
| 183 |
-
self,
|
| 184 |
-
serialized_encrypted_data: cnp.PublicArguments,
|
| 185 |
-
serialized_evaluation_keys: cnp.EvaluationKeys,
|
| 186 |
-
) -> cnp.PublicResult:
|
| 187 |
-
"""Run the model on the server over encrypted data.
|
| 188 |
-
|
| 189 |
-
Args:
|
| 190 |
-
serialized_encrypted_data (cnp.PublicArguments): The encrypted and serialized data.
|
| 191 |
-
serialized_evaluation_keys (cnp.EvaluationKeys): The serialized evaluation keys.
|
| 192 |
-
|
| 193 |
-
Returns:
|
| 194 |
-
cnp.PublicResult: The result of the model.
|
| 195 |
-
"""
|
| 196 |
-
assert_true(self.server is not None, "Model has not been loaded.")
|
| 197 |
-
|
| 198 |
-
deserialized_encrypted_data = self.server.client_specs.unserialize_public_args(
|
| 199 |
-
serialized_encrypted_data
|
| 200 |
-
)
|
| 201 |
-
deserialized_evaluation_keys = cnp.EvaluationKeys.unserialize(serialized_evaluation_keys)
|
| 202 |
-
result = self.server.run(deserialized_encrypted_data, deserialized_evaluation_keys)
|
| 203 |
-
serialized_result = self.server.client_specs.serialize_public_result(result)
|
| 204 |
-
return serialized_result
|
|
|
|
| 1 |
+
"Client-server interface custom implementation for filter models."
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
import json
|
| 4 |
import concrete.numpy as cnp
|
|
|
|
| 5 |
from filters import Filter
|
| 6 |
|
| 7 |
+
from concrete.ml.deployment import FHEModelClient
|
| 8 |
+
from concrete.ml.version import __version__ as CML_VERSION
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
class CustomFHEClient(FHEModelClient):
|
| 12 |
+
"""Client interface to encrypt and decrypt FHE data associated to a Filter."""
|
| 13 |
|
| 14 |
+
def load(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
"""Load the parameters along with the FHE specs."""
|
| 16 |
|
| 17 |
# Load the client
|
| 18 |
self.client = cnp.Client.load(self.path_dir / "client.zip", self.key_dir)
|
| 19 |
|
| 20 |
+
# Load the filter's parameters from the json file
|
| 21 |
+
with (self.path_dir / "serialized_processing.json").open("r", encoding="utf-8") as f:
|
| 22 |
+
serialized_processing = json.load(f)
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
# Make sure the version in serialized_model is the same as CML_VERSION
|
| 25 |
+
assert serialized_processing["cml_version"] == CML_VERSION, (
|
| 26 |
+
f"The version of Concrete ML library ({CML_VERSION}) is different "
|
| 27 |
+
f"from the one used to save the model ({serialized_processing['cml_version']}). "
|
| 28 |
+
"Please update to the proper Concrete ML version.",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
)
|
| 30 |
|
| 31 |
+
# Initialize the filter model using its filter name
|
| 32 |
+
filter_name = serialized_processing["model_post_processing_params"]["filter_name"]
|
| 33 |
+
self.model = Filter(filter_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
return self.model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
filters.py
CHANGED
|
@@ -127,37 +127,45 @@ class _TorchConv2D(nn.Module):
|
|
| 127 |
class Filter:
|
| 128 |
"""Filter class used in the app."""
|
| 129 |
|
| 130 |
-
def __init__(self,
|
| 131 |
"""Initializing the filter class using a given filter.
|
| 132 |
|
| 133 |
Most filters can be found at https://en.wikipedia.org/wiki/Kernel_(image_processing).
|
| 134 |
|
| 135 |
Args:
|
| 136 |
-
|
| 137 |
"""
|
| 138 |
|
| 139 |
assert_true(
|
| 140 |
-
|
| 141 |
f"Unsupported image filter or transformation. Expected one of {*AVAILABLE_FILTERS,}, "
|
| 142 |
-
f"but got {
|
| 143 |
)
|
| 144 |
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
self.onnx_model = None
|
| 147 |
self.fhe_circuit = None
|
| 148 |
self.divide = None
|
| 149 |
self.repeat_out_channels = False
|
| 150 |
|
| 151 |
-
|
|
|
|
| 152 |
self.torch_model = _TorchIdentity()
|
| 153 |
|
| 154 |
-
elif
|
| 155 |
self.torch_model = _TorchInverted()
|
| 156 |
|
| 157 |
-
elif
|
| 158 |
self.torch_model = _TorchRotate()
|
| 159 |
|
| 160 |
-
elif
|
| 161 |
# Define the grayscale weights (RGB order)
|
| 162 |
# These weights were used in PAL and NTSC video systems and can be found at
|
| 163 |
# https://en.wikipedia.org/wiki/Grayscale
|
|
@@ -177,7 +185,7 @@ class Filter:
|
|
| 177 |
# images to have a RGB format, even for grayscaled ones
|
| 178 |
self.repeat_out_channels = True
|
| 179 |
|
| 180 |
-
elif
|
| 181 |
kernel = np.ones((3, 3))
|
| 182 |
|
| 183 |
self.torch_model = _TorchConv2D(kernel, n_out_channels=3, groups=3)
|
|
@@ -185,7 +193,7 @@ class Filter:
|
|
| 185 |
# Define the value used when for dividing the output values in post-processing
|
| 186 |
self.divide = 9
|
| 187 |
|
| 188 |
-
elif
|
| 189 |
kernel = [
|
| 190 |
[0, -1, 0],
|
| 191 |
[-1, 5, -1],
|
|
@@ -194,7 +202,7 @@ class Filter:
|
|
| 194 |
|
| 195 |
self.torch_model = _TorchConv2D(kernel, n_out_channels=3, groups=3)
|
| 196 |
|
| 197 |
-
elif
|
| 198 |
kernel = [
|
| 199 |
[-1, -1, -1],
|
| 200 |
[-1, 9, -1],
|
|
@@ -218,7 +226,9 @@ class Filter:
|
|
| 218 |
generated automatically using a NumpyModule. Default to None.
|
| 219 |
"""
|
| 220 |
# Generate a random representative set of images used for compilation, following Torch's
|
| 221 |
-
# shape format (batch, in_channels, image_height, image_width)
|
|
|
|
|
|
|
| 222 |
np.random.seed(42)
|
| 223 |
inputset = tuple(
|
| 224 |
np.random.randint(0, 255, size=((1, 3) + INPUT_SHAPE), dtype=np.int64) for _ in range(10)
|
|
@@ -249,6 +259,20 @@ class Filter:
|
|
| 249 |
|
| 250 |
return self.fhe_circuit
|
| 251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
def pre_processing(self, input_image):
|
| 253 |
"""Apply pre-processing to the encrypted input images.
|
| 254 |
|
|
@@ -290,72 +314,3 @@ class Filter:
|
|
| 290 |
output_image = output_image.repeat(3, axis=2)
|
| 291 |
|
| 292 |
return output_image
|
| 293 |
-
|
| 294 |
-
@classmethod
|
| 295 |
-
def from_json(cls, json_path):
|
| 296 |
-
"""Instantiate a filter using a json file.
|
| 297 |
-
|
| 298 |
-
Args:
|
| 299 |
-
json_path (Union[str, pathlib.Path]): Path to the json file.
|
| 300 |
-
|
| 301 |
-
Returns:
|
| 302 |
-
model (Filter): The instantiated filter class.
|
| 303 |
-
"""
|
| 304 |
-
# Load the parameters from the json file
|
| 305 |
-
with open(json_path, "r", encoding="utf-8") as f:
|
| 306 |
-
serialized_processing = json.load(f)
|
| 307 |
-
|
| 308 |
-
# Make sure the version in serialized_model is the same as CML_VERSION
|
| 309 |
-
assert_true(
|
| 310 |
-
serialized_processing["cml_version"] == CML_VERSION,
|
| 311 |
-
f"The version of Concrete ML library ({CML_VERSION}) is different "
|
| 312 |
-
f"from the one used to save the model ({serialized_processing['cml_version']}). "
|
| 313 |
-
"Please update to the proper Concrete ML version.",
|
| 314 |
-
)
|
| 315 |
-
|
| 316 |
-
# Initialize the model
|
| 317 |
-
model = cls(image_filter=serialized_processing["model_filter"])
|
| 318 |
-
|
| 319 |
-
return model
|
| 320 |
-
|
| 321 |
-
def to_json(self, path_dir, file_name="serialized_processing"):
|
| 322 |
-
"""Export the parameters to a json file.
|
| 323 |
-
|
| 324 |
-
Args:
|
| 325 |
-
path_dir (Union[str, pathlib.Path]): The path to consider when saving the file.
|
| 326 |
-
file_name (str): The file name
|
| 327 |
-
"""
|
| 328 |
-
# Serialize the parameters
|
| 329 |
-
serialized_processing = {
|
| 330 |
-
"model_filter": self.filter,
|
| 331 |
-
}
|
| 332 |
-
serialized_processing = self._clean_dict_types_for_json(serialized_processing)
|
| 333 |
-
|
| 334 |
-
# Add the version of the current CML library
|
| 335 |
-
serialized_processing["cml_version"] = CML_VERSION
|
| 336 |
-
|
| 337 |
-
# Save the json file
|
| 338 |
-
with open(path_dir / f"{file_name}.json", "w", encoding="utf-8") as f:
|
| 339 |
-
json.dump(serialized_processing, f)
|
| 340 |
-
|
| 341 |
-
def _clean_dict_types_for_json(self, d: dict) -> dict:
|
| 342 |
-
"""Clean all values in the dict to be json serializable.
|
| 343 |
-
|
| 344 |
-
Args:
|
| 345 |
-
d (Dict): The dict to clean
|
| 346 |
-
|
| 347 |
-
Returns:
|
| 348 |
-
Dict: The cleaned dict
|
| 349 |
-
"""
|
| 350 |
-
key_to_delete = []
|
| 351 |
-
for key, value in d.items():
|
| 352 |
-
if isinstance(value, list) and len(value) > 0 and isinstance(value[0], dict):
|
| 353 |
-
d[key] = [self._clean_dict_types_for_json(v) for v in value]
|
| 354 |
-
elif isinstance(value, dict):
|
| 355 |
-
d[key] = self._clean_dict_types_for_json(value)
|
| 356 |
-
elif isinstance(value, (np.generic, np.ndarray)):
|
| 357 |
-
d[key] = d[key].tolist()
|
| 358 |
-
|
| 359 |
-
for key in key_to_delete:
|
| 360 |
-
d.pop(key)
|
| 361 |
-
return d
|
|
|
|
| 127 |
class Filter:
|
| 128 |
"""Filter class used in the app."""
|
| 129 |
|
| 130 |
+
def __init__(self, filter_name):
|
| 131 |
"""Initializing the filter class using a given filter.
|
| 132 |
|
| 133 |
Most filters can be found at https://en.wikipedia.org/wiki/Kernel_(image_processing).
|
| 134 |
|
| 135 |
Args:
|
| 136 |
+
filter_name (str): The filter to consider.
|
| 137 |
"""
|
| 138 |
|
| 139 |
assert_true(
|
| 140 |
+
filter_name in AVAILABLE_FILTERS,
|
| 141 |
f"Unsupported image filter or transformation. Expected one of {*AVAILABLE_FILTERS,}, "
|
| 142 |
+
f"but got {filter_name}",
|
| 143 |
)
|
| 144 |
|
| 145 |
+
# Define attributes needed in order to prevent the Concrete-ML client-server interface
|
| 146 |
+
# from breaking
|
| 147 |
+
self.post_processing_params = {"filter_name": filter_name}
|
| 148 |
+
self.input_quantizers = []
|
| 149 |
+
self.output_quantizers = []
|
| 150 |
+
|
| 151 |
+
# Define attributes associated to the filter
|
| 152 |
+
self.filter = filter_name
|
| 153 |
self.onnx_model = None
|
| 154 |
self.fhe_circuit = None
|
| 155 |
self.divide = None
|
| 156 |
self.repeat_out_channels = False
|
| 157 |
|
| 158 |
+
# Instantiate the torch module associated to the given filter name
|
| 159 |
+
if filter_name == "identity":
|
| 160 |
self.torch_model = _TorchIdentity()
|
| 161 |
|
| 162 |
+
elif filter_name == "inverted":
|
| 163 |
self.torch_model = _TorchInverted()
|
| 164 |
|
| 165 |
+
elif filter_name == "rotate":
|
| 166 |
self.torch_model = _TorchRotate()
|
| 167 |
|
| 168 |
+
elif filter_name == "black and white":
|
| 169 |
# Define the grayscale weights (RGB order)
|
| 170 |
# These weights were used in PAL and NTSC video systems and can be found at
|
| 171 |
# https://en.wikipedia.org/wiki/Grayscale
|
|
|
|
| 185 |
# images to have a RGB format, even for grayscaled ones
|
| 186 |
self.repeat_out_channels = True
|
| 187 |
|
| 188 |
+
elif filter_name == "blur":
|
| 189 |
kernel = np.ones((3, 3))
|
| 190 |
|
| 191 |
self.torch_model = _TorchConv2D(kernel, n_out_channels=3, groups=3)
|
|
|
|
| 193 |
# Define the value used when for dividing the output values in post-processing
|
| 194 |
self.divide = 9
|
| 195 |
|
| 196 |
+
elif filter_name == "sharpen":
|
| 197 |
kernel = [
|
| 198 |
[0, -1, 0],
|
| 199 |
[-1, 5, -1],
|
|
|
|
| 202 |
|
| 203 |
self.torch_model = _TorchConv2D(kernel, n_out_channels=3, groups=3)
|
| 204 |
|
| 205 |
+
elif filter_name == "ridge detection":
|
| 206 |
kernel = [
|
| 207 |
[-1, -1, -1],
|
| 208 |
[-1, 9, -1],
|
|
|
|
| 226 |
generated automatically using a NumpyModule. Default to None.
|
| 227 |
"""
|
| 228 |
# Generate a random representative set of images used for compilation, following Torch's
|
| 229 |
+
# shape format (batch, in_channels, image_height, image_width) for each samples
|
| 230 |
+
# This version's compiler only handles tuples of 1-batch array as inputset, meaning we need
|
| 231 |
+
# to define the inputset as a Tuple[np.ndarray[shape=(1, 3, H, W)]]
|
| 232 |
np.random.seed(42)
|
| 233 |
inputset = tuple(
|
| 234 |
np.random.randint(0, 255, size=((1, 3) + INPUT_SHAPE), dtype=np.int64) for _ in range(10)
|
|
|
|
| 259 |
|
| 260 |
return self.fhe_circuit
|
| 261 |
|
| 262 |
+
def quantize_input(self, input_image):
|
| 263 |
+
"""Quantize the input.
|
| 264 |
+
|
| 265 |
+
Images are already quantized in this case, however we need to define this method in order
|
| 266 |
+
to prevent the Concrete-ML client-server interface from breaking.
|
| 267 |
+
|
| 268 |
+
Args:
|
| 269 |
+
input_image (np.ndarray): The input to quantize.
|
| 270 |
+
|
| 271 |
+
Returns:
|
| 272 |
+
np.ndarray: The quantized input.
|
| 273 |
+
"""
|
| 274 |
+
return input_image
|
| 275 |
+
|
| 276 |
def pre_processing(self, input_image):
|
| 277 |
"""Apply pre-processing to the encrypted input images.
|
| 278 |
|
|
|
|
| 314 |
output_image = output_image.repeat(3, axis=2)
|
| 315 |
|
| 316 |
return output_image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
filters/black and white/deployment/client.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 388
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5c6a88c9717d1ec81035e715ad84803b2351756c1a1a6fb51786e2e07b8cbe84
|
| 3 |
size 388
|
filters/black and white/deployment/serialized_processing.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"
|
|
|
|
| 1 |
+
{"model_type": "Filter", "model_post_processing_params": {"filter_name": "black and white"}, "input_quantizers": [], "output_quantizers": [], "cml_version": "0.6.1"}
|
filters/black and white/deployment/server.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 4364
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3efb2af650a4b4690a8096048b084a03f9a505985fc8dee4da7d3367ea040918
|
| 3 |
size 4364
|
filters/black_and_white/deployment/client.zip
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:000285a62f642b20eda541c6697e33de3d725c254ff5c2098e3157fc73cd017b
|
| 3 |
-
size 388
|
|
|
|
|
|
|
|
|
|
|
|
filters/black_and_white/deployment/serialized_processing.json
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
{"model_filter": "black_and_white", "cml_version": "0.6.0-rc0"}
|
|
|
|
|
|
filters/black_and_white/deployment/server.zip
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:9867657ff1e7b2c8eb3c72f28be8b8e8ee0b355762b99f34a25a2c9de0cb104c
|
| 3 |
-
size 4762
|
|
|
|
|
|
|
|
|
|
|
|
filters/black_and_white/server.onnx
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3f4774c394a6fec8cc43dae14ce627837aa998fcc78ba4ab67ad1c5bf92dd3ee
|
| 3 |
-
size 336
|
|
|
|
|
|
|
|
|
|
|
|
filters/blur/deployment/client.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 391
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f724173d51c70053038bd40ce911ce4ef9ea50a70b077129b86abb482ad4a21e
|
| 3 |
size 391
|
filters/blur/deployment/serialized_processing.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"
|
|
|
|
| 1 |
+
{"model_type": "Filter", "model_post_processing_params": {"filter_name": "blur"}, "input_quantizers": [], "output_quantizers": [], "cml_version": "0.6.1"}
|
filters/blur/deployment/server.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 7263
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1193734d14b02195075fc402e3d84e11b8b7216a83ceff9a0becb16f1b3fbcf0
|
| 3 |
size 7263
|
filters/identity/deployment/client.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 378
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e396e33163faf6dbf2a8eca318908efa92dea5d0d8c24a46439a925497543431
|
| 3 |
size 378
|
filters/identity/deployment/serialized_processing.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"
|
|
|
|
| 1 |
+
{"model_type": "Filter", "model_post_processing_params": {"filter_name": "identity"}, "input_quantizers": [], "output_quantizers": [], "cml_version": "0.6.1"}
|
filters/identity/deployment/server.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 2559
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:da52e793a997ded3b0c383f246da31d51317e2461ff1955f9f01014258272f9b
|
| 3 |
size 2559
|
filters/inverted/deployment/client.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 378
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e396e33163faf6dbf2a8eca318908efa92dea5d0d8c24a46439a925497543431
|
| 3 |
size 378
|
filters/inverted/deployment/serialized_processing.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"
|
|
|
|
| 1 |
+
{"model_type": "Filter", "model_post_processing_params": {"filter_name": "inverted"}, "input_quantizers": [], "output_quantizers": [], "cml_version": "0.6.1"}
|
filters/inverted/deployment/server.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 4179
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe95edd2998cee4ff7e40fde889f7a85bbf69218a4f1b517565de79d82517c4f
|
| 3 |
size 4179
|
filters/ridge detection/deployment/client.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 397
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c1bf9931bbf568d5b74fd3e1bab8fcd48780c88b2f36289b26976cb1ebf4c665
|
| 3 |
size 397
|
filters/ridge detection/deployment/serialized_processing.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"
|
|
|
|
| 1 |
+
{"model_type": "Filter", "model_post_processing_params": {"filter_name": "ridge detection"}, "input_quantizers": [], "output_quantizers": [], "cml_version": "0.6.1"}
|
filters/ridge detection/deployment/server.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 5043
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a31acdba1d94ec7fec833fc8e0b0de7a6b345c9dfef5d0b1a7cbdf30613fdc44
|
| 3 |
size 5043
|
filters/ridge_detection/deployment/client.zip
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:d241694b8c01dce2ad8a5ce2dbe12190e40d6912e88d086dbc0e047aba4dfafb
|
| 3 |
-
size 397
|
|
|
|
|
|
|
|
|
|
|
|
filters/ridge_detection/deployment/serialized_processing.json
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
{"model_filter": "ridge_detection", "cml_version": "0.6.0-rc0"}
|
|
|
|
|
|
filters/ridge_detection/deployment/server.zip
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3605e14d8533e3c57edf30a7da32d4441fcb68228a8ebd028015338b8b5d5f70
|
| 3 |
-
size 4884
|
|
|
|
|
|
|
|
|
|
|
|
filters/ridge_detection/server.onnx
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:1e05d56c4988abd621aee6dea4efe2dfdaf1d09dfb78bb7bf7b6bb3a00d3e80b
|
| 3 |
-
size 532
|
|
|
|
|
|
|
|
|
|
|
|
filters/rotate/deployment/client.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 378
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e396e33163faf6dbf2a8eca318908efa92dea5d0d8c24a46439a925497543431
|
| 3 |
size 378
|
filters/rotate/deployment/serialized_processing.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"
|
|
|
|
| 1 |
+
{"model_type": "Filter", "model_post_processing_params": {"filter_name": "rotate"}, "input_quantizers": [], "output_quantizers": [], "cml_version": "0.6.1"}
|
filters/rotate/deployment/server.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 4431
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fa91ba0d7021fcc6237cd628b6f03151b05625f377149d3d0eedd4a124407646
|
| 3 |
size 4431
|
filters/sharpen/deployment/client.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 396
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4c5dee467fb63804731e998a11323a460d2fc8a9f08f4480d9d9c6deb4431447
|
| 3 |
size 396
|
filters/sharpen/deployment/serialized_processing.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"
|
|
|
|
| 1 |
+
{"model_type": "Filter", "model_post_processing_params": {"filter_name": "sharpen"}, "input_quantizers": [], "output_quantizers": [], "cml_version": "0.6.1"}
|
filters/sharpen/deployment/server.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 7311
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:87285146e97b8787261a7aa15db77819ac9e10a8c165708792db682d6a5072c7
|
| 3 |
size 7311
|
generate_dev_files.py
CHANGED
|
@@ -3,22 +3,22 @@
|
|
| 3 |
import shutil
|
| 4 |
import onnx
|
| 5 |
from common import AVAILABLE_FILTERS, FILTERS_PATH
|
| 6 |
-
from custom_client_server import CustomFHEDev
|
| 7 |
from filters import Filter
|
|
|
|
| 8 |
|
| 9 |
print("Generating deployment files for all available filters")
|
| 10 |
|
| 11 |
-
for
|
| 12 |
-
print("Filter:",
|
| 13 |
|
| 14 |
# Create the filter instance
|
| 15 |
-
filter = Filter(
|
| 16 |
|
| 17 |
# Compile the model on a representative inputset
|
| 18 |
filter.compile()
|
| 19 |
|
| 20 |
# Define the directory path associated to this filter
|
| 21 |
-
filter_path = FILTERS_PATH /
|
| 22 |
|
| 23 |
# Define the directory path associated to this filter's deployment files
|
| 24 |
deployment_path = filter_path / "deployment"
|
|
@@ -28,7 +28,7 @@ for image_filter in AVAILABLE_FILTERS:
|
|
| 28 |
shutil.rmtree(deployment_path)
|
| 29 |
|
| 30 |
# Save the files needed for deployment
|
| 31 |
-
fhe_dev_filter =
|
| 32 |
fhe_dev_filter.save()
|
| 33 |
|
| 34 |
# Save the ONNX model
|
|
|
|
| 3 |
import shutil
|
| 4 |
import onnx
|
| 5 |
from common import AVAILABLE_FILTERS, FILTERS_PATH
|
|
|
|
| 6 |
from filters import Filter
|
| 7 |
+
from concrete.ml.deployment import FHEModelDev
|
| 8 |
|
| 9 |
print("Generating deployment files for all available filters")
|
| 10 |
|
| 11 |
+
for filter_name in AVAILABLE_FILTERS:
|
| 12 |
+
print("Filter:", filter_name, "\n")
|
| 13 |
|
| 14 |
# Create the filter instance
|
| 15 |
+
filter = Filter(filter_name)
|
| 16 |
|
| 17 |
# Compile the model on a representative inputset
|
| 18 |
filter.compile()
|
| 19 |
|
| 20 |
# Define the directory path associated to this filter
|
| 21 |
+
filter_path = FILTERS_PATH / filter_name
|
| 22 |
|
| 23 |
# Define the directory path associated to this filter's deployment files
|
| 24 |
deployment_path = filter_path / "deployment"
|
|
|
|
| 28 |
shutil.rmtree(deployment_path)
|
| 29 |
|
| 30 |
# Save the files needed for deployment
|
| 31 |
+
fhe_dev_filter = FHEModelDev(deployment_path, filter)
|
| 32 |
fhe_dev_filter.save()
|
| 33 |
|
| 34 |
# Save the ONNX model
|
server.py
CHANGED
|
@@ -4,24 +4,24 @@ import time
|
|
| 4 |
from typing import List
|
| 5 |
|
| 6 |
from common import FILTERS_PATH, SERVER_TMP_PATH
|
| 7 |
-
from custom_client_server import CustomFHEServer
|
| 8 |
from fastapi import FastAPI, File, Form, UploadFile
|
| 9 |
from fastapi.responses import JSONResponse, Response
|
| 10 |
from pydantic import BaseModel
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
-
def get_server_file_path(name, user_id,
|
| 14 |
"""Get the correct temporary file path for the server.
|
| 15 |
|
| 16 |
Args:
|
| 17 |
name (str): The desired file name.
|
| 18 |
user_id (int): The current user's ID.
|
| 19 |
-
|
| 20 |
|
| 21 |
Returns:
|
| 22 |
pathlib.Path: The file path.
|
| 23 |
"""
|
| 24 |
-
return SERVER_TMP_PATH / f"{name}_{
|
| 25 |
|
| 26 |
|
| 27 |
class FilterRequest(BaseModel):
|
|
@@ -74,7 +74,7 @@ def run_fhe(
|
|
| 74 |
evaluation_key = evaluation_key_file.read()
|
| 75 |
|
| 76 |
# Load the FHE server
|
| 77 |
-
fhe_server =
|
| 78 |
|
| 79 |
# Run the FHE execution
|
| 80 |
start = time.time()
|
|
|
|
| 4 |
from typing import List
|
| 5 |
|
| 6 |
from common import FILTERS_PATH, SERVER_TMP_PATH
|
|
|
|
| 7 |
from fastapi import FastAPI, File, Form, UploadFile
|
| 8 |
from fastapi.responses import JSONResponse, Response
|
| 9 |
from pydantic import BaseModel
|
| 10 |
+
from concrete.ml.deployment import FHEModelServer
|
| 11 |
|
| 12 |
|
| 13 |
+
def get_server_file_path(name, user_id, filter_name):
|
| 14 |
"""Get the correct temporary file path for the server.
|
| 15 |
|
| 16 |
Args:
|
| 17 |
name (str): The desired file name.
|
| 18 |
user_id (int): The current user's ID.
|
| 19 |
+
filter_name (str): The filter chosen by the user
|
| 20 |
|
| 21 |
Returns:
|
| 22 |
pathlib.Path: The file path.
|
| 23 |
"""
|
| 24 |
+
return SERVER_TMP_PATH / f"{name}_{filter_name}_{user_id}"
|
| 25 |
|
| 26 |
|
| 27 |
class FilterRequest(BaseModel):
|
|
|
|
| 74 |
evaluation_key = evaluation_key_file.read()
|
| 75 |
|
| 76 |
# Load the FHE server
|
| 77 |
+
fhe_server = FHEModelServer(FILTERS_PATH / f"{filter}/deployment")
|
| 78 |
|
| 79 |
# Run the FHE execution
|
| 80 |
start = time.time()
|