|
import gradio as gr |
|
import os |
|
import yaml |
|
import pandas as pd |
|
from src.face_texture import GetFaceTexture |
|
from src.face_symmetry import GetFaceSymmetry |
|
from src.face_demographics import GetFaceDemographics |
|
from src.face_proportions import GetFaceProportions |
|
from PIL import Image as PILImage |
|
from typing import List, Any |
|
|
|
|
|
def get_results(image_input: PILImage.Image) -> List[Any]: |
|
demographics_dict = GetFaceDemographics().main(image_input) |
|
( |
|
ratios_dict, |
|
face_landmarks_image, |
|
) = GetFaceProportions().main(image_input) |
|
face_symmetry_image, symmetry_dict = GetFaceSymmetry().main(image_input) |
|
face_image, face_texture_image, texture_dict = GetFaceTexture().main(image_input) |
|
|
|
results = { |
|
"Demographic predictions": demographics_dict, |
|
"Face proportions": ratios_dict, |
|
"Face symmetry metrics": symmetry_dict, |
|
"Face texture metrics": texture_dict, |
|
} |
|
|
|
return ( |
|
results, |
|
face_image, |
|
face_landmarks_image, |
|
face_symmetry_image, |
|
face_texture_image, |
|
) |
|
|
|
|
|
def concatenate_image( |
|
image_1: PILImage.Image, image_2: PILImage.Image |
|
) -> PILImage.Image: |
|
image = PILImage.new("RGB", (image_1.width + image_2.width, image_1.height)) |
|
image.paste(image_1, (0, 0)) |
|
image.paste(image_2, (image_1.width, 0)) |
|
return image |
|
|
|
|
|
def get_dict_child_data(results_image: dict, image_number: int) -> dict: |
|
flattened_data = {"image": f"Face {image_number}"} |
|
for key, sub_dict in results_image.items(): |
|
for sub_key, value in sub_dict.items(): |
|
flattened_data[sub_key] = value |
|
return flattened_data |
|
|
|
|
|
def output_fn( |
|
image_input_1: PILImage.Image, image_input_2: PILImage.Image |
|
) -> List[Any]: |
|
with open("parameters.yml", "r") as file: |
|
data = yaml.safe_load(file) |
|
results_interpretation = data["results_interpretation"] |
|
|
|
if image_input_1 is not None and image_input_2 is not None: |
|
( |
|
results_image_1, |
|
face_image_1, |
|
face_landmarks_image_1, |
|
face_symmetry_image_1, |
|
face_texture_image_1, |
|
) = get_results(image_input_1) |
|
( |
|
results_image_2, |
|
face_image_2, |
|
face_landmarks_image_2, |
|
face_symmetry_image_2, |
|
face_texture_image_2, |
|
) = get_results(image_input_2) |
|
results_image_1, results_image_2 = get_dict_child_data( |
|
results_image_1, 1 |
|
), get_dict_child_data(results_image_2, 2) |
|
results_df = pd.DataFrame([results_image_1, results_image_2]) |
|
face_image = concatenate_image(face_image_1, face_image_2) |
|
face_landmarks_image = concatenate_image( |
|
face_landmarks_image_1, face_landmarks_image_2 |
|
) |
|
face_symmetry_image = concatenate_image( |
|
face_symmetry_image_1, face_symmetry_image_2 |
|
) |
|
face_texture_image = concatenate_image( |
|
face_texture_image_1, face_texture_image_2 |
|
) |
|
|
|
if image_input_1 == None and image_input_2 is not None: |
|
( |
|
results, |
|
face_image, |
|
face_landmarks_image, |
|
face_symmetry_image, |
|
face_texture_image, |
|
) = get_results(image_input_2) |
|
results_df = pd.DataFrame([get_dict_child_data(results, 2)]) |
|
|
|
if image_input_2 == None and image_input_1 is not None: |
|
( |
|
results, |
|
face_image, |
|
face_landmarks_image, |
|
face_symmetry_image, |
|
face_texture_image, |
|
) = get_results(image_input_1) |
|
results_df = pd.DataFrame([get_dict_child_data(results, 1)]) |
|
|
|
return ( |
|
results_df, |
|
results_interpretation, |
|
face_image, |
|
face_landmarks_image, |
|
face_symmetry_image, |
|
face_texture_image, |
|
) |
|
|
|
|
|
gigi_hadid = os.path.join(os.path.dirname(__file__), "data/gigi_hadid.webp") |
|
|
|
iface = gr.Interface( |
|
fn=output_fn, |
|
inputs=[ |
|
gr.Image(type="pil", label="Upload Face 1", value=gigi_hadid), |
|
gr.Image(type="pil", label="Upload Face 2"), |
|
], |
|
outputs=[ |
|
gr.DataFrame(label="Results"), |
|
gr.JSON(label="Results explainer"), |
|
gr.Image(type="pil", label="Extracted face"), |
|
gr.Image(type="pil", label="Face landmarks"), |
|
gr.Image(type="pil", label="Face symmetry"), |
|
gr.Image(type="pil", label="Extracted face texture"), |
|
], |
|
title="Advanced Facial Feature Detector", |
|
description=""" |
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>JSON Output in HTML</title> |
|
<style> |
|
.section { |
|
margin-bottom: 20px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
|
|
<div class="section"> |
|
<font size="3"> |
|
<h3><center>Turn your selfie into insights! Discover age and gender predictions, symmetry evaluations, and detailed proportions and texture analyses with our app.</center></h3> |
|
<hr style="margin-top: 20px; margin-bottom: 20px;"> |
|
<p><strong>Instructions:</strong> Upload up to 2 photos. For optimal results, upload a clear front-facing image (see example). To do so, either drag and drop your photo or click <i>Upload Face</i>, then press <i>Submit</i>.</p> |
|
<p><strong>Other information:</strong></p> |
|
<ul> |
|
<li>The output computation requires approximately 5 to 30 seconds.</li> |
|
<li>No uploaded photo is stored.</li> |
|
<li>If an error occurs try again or try a different photo or angle.</li> |
|
<li>Once submitted, a section detailing the results and associated images will be displayed.</li> |
|
</ul> |
|
</font> |
|
</div> |
|
</body> |
|
</html> |
|
""", |
|
theme=gr.themes.Soft(), |
|
live=False, |
|
) |
|
|
|
iface.launch() |
|
|