added license of mtlora and perception encoders code
Browse files- app.py +31 -51
- core/LICENSE_PE +201 -0
- src/LICENSE_MTLORA +21 -0
app.py
CHANGED
|
@@ -10,8 +10,8 @@ import numpy as np
|
|
| 10 |
from PIL import Image, ImageDraw, ImageFont
|
| 11 |
import base64
|
| 12 |
from io import BytesIO
|
| 13 |
-
import traceback
|
| 14 |
-
from huggingface_hub import snapshot_download
|
| 15 |
from utils.face_detector import FaceDetector
|
| 16 |
|
| 17 |
# Class definitions
|
|
@@ -36,7 +36,7 @@ model = None
|
|
| 36 |
transform = None
|
| 37 |
detector = None
|
| 38 |
device = None
|
| 39 |
-
current_ckpt_dir = None
|
| 40 |
CHECKPOINTS_DIR = './checkpoints/'
|
| 41 |
MODEL_REPO_ID = "Antuke/FaR-FT-PE"
|
| 42 |
|
|
@@ -50,14 +50,13 @@ def scan_checkpoints(ckpt_dir):
|
|
| 50 |
ckpt_files = [
|
| 51 |
os.path.join(ckpt_dir, f)
|
| 52 |
for f in sorted(os.listdir(ckpt_dir))
|
| 53 |
-
if f.endswith(('.pt', '.pth'))
|
| 54 |
]
|
| 55 |
except Exception as e:
|
| 56 |
print(f"Error scanning checkpoint directory {ckpt_dir}: {e}")
|
| 57 |
return [], None
|
| 58 |
|
| 59 |
-
|
| 60 |
-
# label = filename (e.g., "mtlora.pt"), value = full path
|
| 61 |
choices_list = [(os.path.basename(f), f) for f in ckpt_files]
|
| 62 |
|
| 63 |
default_ckpt_path = os.path.join(ckpt_dir, 'mtlora.pt')
|
|
@@ -65,7 +64,7 @@ def scan_checkpoints(ckpt_dir):
|
|
| 65 |
if default_ckpt_path in ckpt_files:
|
| 66 |
return choices_list, default_ckpt_path
|
| 67 |
elif ckpt_files:
|
| 68 |
-
return choices_list, ckpt_files[0]
|
| 69 |
else:
|
| 70 |
print(f"No checkpoints found in {ckpt_dir}")
|
| 71 |
return [], None
|
|
@@ -82,16 +81,13 @@ def load_model(device,ckpt_dir='./checkpoints/mtlora.pt', pe_vision_config="PE-C
|
|
| 82 |
return model,transform
|
| 83 |
|
| 84 |
def load_model_and_update_status(model_filepath):
|
| 85 |
-
"""
|
| 86 |
-
Wrapper function to load a model *from a local file path* and return a status.
|
| 87 |
-
The file path is provided by the dropdown.
|
| 88 |
-
"""
|
| 89 |
global model, current_ckpt_dir
|
| 90 |
|
| 91 |
if model_filepath is None or model_filepath == "":
|
| 92 |
return "No checkpoint selected."
|
| 93 |
|
| 94 |
-
# Check if this model
|
| 95 |
if model is not None and model_filepath == current_ckpt_dir:
|
| 96 |
status = f"Model already loaded: {os.path.basename(model_filepath)}"
|
| 97 |
print(status)
|
|
@@ -99,11 +95,8 @@ def load_model_and_update_status(model_filepath):
|
|
| 99 |
|
| 100 |
gr.Info(f"Loading model: {os.path.basename(model_filepath)}...")
|
| 101 |
try:
|
| 102 |
-
|
| 103 |
-
# The file is already local. Just initialize it.
|
| 104 |
-
# The 'model_filepath' *is* the ckpt_dir for init_model
|
| 105 |
init_model(ckpt_dir=model_filepath, detection_confidence=0.5)
|
| 106 |
-
# --- End of new logic ---
|
| 107 |
|
| 108 |
current_ckpt_dir = model_filepath # Set global path on successful load
|
| 109 |
status = f"Successfully loaded: {os.path.basename(model_filepath)}"
|
|
@@ -170,10 +163,14 @@ def predict(model, image):
|
|
| 170 |
return results
|
| 171 |
|
| 172 |
def get_centroid_weighted_age(probs):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
probs = list(probs.values())
|
| 174 |
centroids = [1, 4.5, 14.5, 24.5, 34.5, 44.5, 54.5, 64.5, 80]
|
| 175 |
age = 0
|
| 176 |
-
|
| 177 |
for i,p in enumerate(probs):
|
| 178 |
age += p * centroids[i]
|
| 179 |
|
|
@@ -191,7 +188,6 @@ def init_model(ckpt_dir="./checkpoints/mtlora.pt", detection_confidence=0.5):
|
|
| 191 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 192 |
print(f"Using device: {device}")
|
| 193 |
|
| 194 |
-
# Verify model weights exist
|
| 195 |
if not os.path.exists(ckpt_dir):
|
| 196 |
error_msg = f"Model weights not found: {ckpt_dir}."
|
| 197 |
print(f"ERROR: {error_msg}")
|
|
@@ -226,8 +222,6 @@ def process_image(image, selected_checkpoint_path):
|
|
| 226 |
return None, "<p style='color: red;'>Please upload an image</p>"
|
| 227 |
|
| 228 |
# Ensure model is initialized
|
| 229 |
-
# This check is crucial. If the user changes the dropdown, it triggers
|
| 230 |
-
# load_model_and_update_status. If they just hit "Classify",
|
| 231 |
# this check ensures the selected model is loaded.
|
| 232 |
if model is None or selected_checkpoint_path != current_ckpt_dir:
|
| 233 |
print(f"Model mismatch or not loaded. Selected: {selected_checkpoint_path}, Current: {current_ckpt_dir}")
|
|
@@ -237,26 +231,24 @@ def process_image(image, selected_checkpoint_path):
|
|
| 237 |
|
| 238 |
|
| 239 |
try:
|
| 240 |
-
# --- 1. Prepare images for detection and drawing ---
|
| 241 |
|
| 242 |
# Convert PIL to OpenCV format (BGR) for the detector
|
| 243 |
if isinstance(image, Image.Image):
|
| 244 |
img_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
| 245 |
else:
|
| 246 |
-
# Assuming it's a numpy array from Gradio webcam
|
| 247 |
img_cv = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
| 248 |
|
| 249 |
# Create a PIL copy to draw annotations on
|
| 250 |
img_pil_annotated = image.copy()
|
| 251 |
draw = ImageDraw.Draw(img_pil_annotated)
|
| 252 |
|
| 253 |
-
# ---
|
| 254 |
faces = detector.detect(img_cv, pad_rect=True)
|
| 255 |
|
| 256 |
if faces is None or len(faces) == 0:
|
| 257 |
return image, "<p style='color: orange;'>No faces detected in the image</p>"
|
| 258 |
|
| 259 |
-
# ---
|
| 260 |
crops_pil = []
|
| 261 |
face_data = []
|
| 262 |
|
|
@@ -265,33 +257,32 @@ def process_image(image, selected_checkpoint_path):
|
|
| 265 |
crop_pil = Image.fromarray(crop_rgb)
|
| 266 |
crops_pil.append(crop_pil)
|
| 267 |
|
| 268 |
-
# Resize crop to 336x336 for display
|
| 269 |
crop_resized = crop_pil.resize((336, 336), Image.Resampling.LANCZOS)
|
| 270 |
|
| 271 |
face_data.append({
|
| 272 |
'bbox': bbox,
|
| 273 |
'detection_confidence': float(confidence),
|
| 274 |
-
'crop_image': crop_resized
|
| 275 |
})
|
| 276 |
|
| 277 |
-
# ---
|
| 278 |
crop_tensors = [transform(crop_pil) for crop_pil in crops_pil]
|
| 279 |
batch_tensor = torch.stack(crop_tensors).to(device)
|
| 280 |
|
| 281 |
-
# Get predictions
|
| 282 |
predictions = predict(model, batch_tensor)
|
| 283 |
|
| 284 |
# Combine face data with predictions
|
| 285 |
for face, pred in zip(face_data, predictions):
|
| 286 |
face['predictions'] = pred
|
| 287 |
|
| 288 |
-
# ---
|
| 289 |
for idx, face in enumerate(face_data):
|
| 290 |
bbox = face['bbox']
|
| 291 |
pred = face['predictions']
|
| 292 |
x, y, w, h = bbox
|
| 293 |
|
| 294 |
-
# --- Calculate Adaptive Font
|
| 295 |
font_size_ratio = 0.08
|
| 296 |
min_font_size = 12
|
| 297 |
max_font_size = 48
|
|
@@ -323,16 +314,15 @@ def process_image(image, selected_checkpoint_path):
|
|
| 323 |
lines_to_draw.append(f"Emotion: {emo_label} ({emo_conf*100:.0f}%)")
|
| 324 |
|
| 325 |
|
| 326 |
-
# --- Calculate total height of the text block
|
| 327 |
line_spacing = 10
|
| 328 |
total_text_height = 0
|
| 329 |
for line in lines_to_draw:
|
| 330 |
_left, top, _right, bottom = draw.textbbox((0, 0), line, font=font)
|
| 331 |
total_text_height += (bottom - top) + line_spacing
|
| 332 |
|
| 333 |
-
# --- Place text ABOVE or BELOW the box
|
| 334 |
if y - total_text_height > 0:
|
| 335 |
-
# PLACE TEXT ABOVE: There is enough space
|
| 336 |
text_y = y - line_spacing
|
| 337 |
for line in reversed(lines_to_draw):
|
| 338 |
left, top, right, bottom = draw.textbbox((x, text_y), line, font=font, anchor="ls") # anchor left-baseline
|
|
@@ -340,7 +330,6 @@ def process_image(image, selected_checkpoint_path):
|
|
| 340 |
draw.text((x, text_y), line, font=font, fill="white", anchor="ls")
|
| 341 |
text_y = top - line_spacing # Move y-position up for the next line
|
| 342 |
else:
|
| 343 |
-
# PLACE TEXT BELOW: Not enough space above, so draw downwards
|
| 344 |
text_y = y + h + line_spacing
|
| 345 |
for line in lines_to_draw:
|
| 346 |
left, top, right, bottom = draw.textbbox((x, text_y), line, font=font, anchor="lt")
|
|
@@ -348,7 +337,6 @@ def process_image(image, selected_checkpoint_path):
|
|
| 348 |
draw.text((x, text_y), line, font=font, fill="white", anchor="lt")
|
| 349 |
text_y = bottom + line_spacing
|
| 350 |
|
| 351 |
-
# --- 6. Create HTML results ---
|
| 352 |
|
| 353 |
# Helper function to convert PIL image to base64
|
| 354 |
def pil_to_base64(img_pil):
|
|
@@ -357,7 +345,6 @@ def process_image(image, selected_checkpoint_path):
|
|
| 357 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
| 358 |
return f"data:image/jpeg;base64,{img_str}"
|
| 359 |
|
| 360 |
-
# (HTML Generation code remains the same as before)
|
| 361 |
results_html = f"""
|
| 362 |
<style>
|
| 363 |
:root {{
|
|
@@ -609,7 +596,7 @@ def process_image(image, selected_checkpoint_path):
|
|
| 609 |
"""
|
| 610 |
results_html += "</div>"
|
| 611 |
|
| 612 |
-
# ---
|
| 613 |
return img_pil_annotated, results_html
|
| 614 |
|
| 615 |
except Exception as e:
|
|
@@ -631,7 +618,6 @@ def create_interface(checkpoint_list, default_checkpoint, initial_status):
|
|
| 631 |
"""
|
| 632 |
|
| 633 |
# Create interface
|
| 634 |
-
# --- FIX: Added theme=gr.themes.Default() to enable light/dark mode switching ---
|
| 635 |
with gr.Blocks(css=custom_css, title="Face Classification System", theme=gr.themes.Default()) as demo:
|
| 636 |
|
| 637 |
with gr.Row():
|
|
@@ -642,8 +628,8 @@ def create_interface(checkpoint_list, default_checkpoint, initial_status):
|
|
| 642 |
with gr.Column(scale=3):
|
| 643 |
checkpoint_dropdown = gr.Dropdown(
|
| 644 |
label="Select Model Checkpoint",
|
| 645 |
-
choices=checkpoint_list,
|
| 646 |
-
value=default_checkpoint,
|
| 647 |
)
|
| 648 |
with gr.Column(scale=2):
|
| 649 |
model_status_text = gr.Textbox(
|
|
@@ -698,7 +684,6 @@ def create_interface(checkpoint_list, default_checkpoint, initial_status):
|
|
| 698 |
size="lg"
|
| 699 |
)
|
| 700 |
|
| 701 |
-
# Examples - after button
|
| 702 |
# Dynamically load example images from example directory
|
| 703 |
example_dir = "example"
|
| 704 |
example_images = []
|
|
@@ -720,7 +705,7 @@ def create_interface(checkpoint_list, default_checkpoint, initial_status):
|
|
| 720 |
cache_examples=False
|
| 721 |
)
|
| 722 |
|
| 723 |
-
# Results section
|
| 724 |
with gr.Row():
|
| 725 |
with gr.Column(scale=1):
|
| 726 |
output_html = gr.HTML(
|
|
@@ -731,13 +716,13 @@ def create_interface(checkpoint_list, default_checkpoint, initial_status):
|
|
| 731 |
# Event handlers
|
| 732 |
analyze_btn.click(
|
| 733 |
fn=process_image,
|
| 734 |
-
inputs=[input_image, checkpoint_dropdown],
|
| 735 |
outputs=[output_image, output_html]
|
| 736 |
)
|
| 737 |
|
| 738 |
checkpoint_dropdown.change(
|
| 739 |
fn=load_model_and_update_status,
|
| 740 |
-
inputs=[checkpoint_dropdown],
|
| 741 |
outputs=[model_status_text]
|
| 742 |
)
|
| 743 |
|
|
@@ -764,10 +749,8 @@ try:
|
|
| 764 |
except Exception as e:
|
| 765 |
print(f"CRITICAL: Failed to download models from Hub. {e}")
|
| 766 |
traceback.print_exc()
|
| 767 |
-
# --- End of NEW code ---
|
| 768 |
|
| 769 |
|
| 770 |
-
# --- 1. Scan for models first ---
|
| 771 |
# This will now find the files you just downloaded
|
| 772 |
checkpoint_list, default_checkpoint = scan_checkpoints(CHECKPOINTS_DIR)
|
| 773 |
|
|
@@ -777,7 +760,7 @@ else:
|
|
| 777 |
print(f"Found checkpoints: {len(checkpoint_list)} file(s).")
|
| 778 |
print(f"Default checkpoint: {default_checkpoint}")
|
| 779 |
|
| 780 |
-
# ---
|
| 781 |
initial_status_msg = "No default model found. Please select one."
|
| 782 |
if default_checkpoint:
|
| 783 |
print(f"\nInitializing default model: {default_checkpoint}")
|
|
@@ -789,7 +772,7 @@ else:
|
|
| 789 |
print("⚠ Warning: No default model to load.")
|
| 790 |
|
| 791 |
|
| 792 |
-
# ---
|
| 793 |
print("Creating Gradio interface...")
|
| 794 |
demo = create_interface(checkpoint_list, default_checkpoint, initial_status_msg)
|
| 795 |
print("✓ Interface created successfully!")
|
|
@@ -799,7 +782,6 @@ if __name__ == "__main__":
|
|
| 799 |
import argparse
|
| 800 |
|
| 801 |
parser = argparse.ArgumentParser(description="VLM Soft Biometrics - Gradio Interface")
|
| 802 |
-
# ckpt_dir is now managed by the startup download, so this arg is less relevant
|
| 803 |
parser.add_argument("--ckpt_dir", type=str, default="./checkpoints/",
|
| 804 |
help="Path to the checkpoint directory (will be populated from HF Hub)")
|
| 805 |
parser.add_argument("--detection_confidence", type=float, default=0.5,
|
|
@@ -812,9 +794,7 @@ if __name__ == "__main__":
|
|
| 812 |
help="Server name/IP to bind to")
|
| 813 |
args = parser.parse_args()
|
| 814 |
|
| 815 |
-
# Update global config if args are provided
|
| 816 |
CHECKPOINTS_DIR = args.ckpt_dir
|
| 817 |
-
# Note: detection_confidence is passed to init_model during load, so it's handled.
|
| 818 |
|
| 819 |
print(f"\nLaunching server on {args.server_name}:{args.port}")
|
| 820 |
print(f"Monitoring checkpoint directory: {CHECKPOINTS_DIR}")
|
|
|
|
| 10 |
from PIL import Image, ImageDraw, ImageFont
|
| 11 |
import base64
|
| 12 |
from io import BytesIO
|
| 13 |
+
import traceback
|
| 14 |
+
from huggingface_hub import snapshot_download
|
| 15 |
from utils.face_detector import FaceDetector
|
| 16 |
|
| 17 |
# Class definitions
|
|
|
|
| 36 |
transform = None
|
| 37 |
detector = None
|
| 38 |
device = None
|
| 39 |
+
current_ckpt_dir = None
|
| 40 |
CHECKPOINTS_DIR = './checkpoints/'
|
| 41 |
MODEL_REPO_ID = "Antuke/FaR-FT-PE"
|
| 42 |
|
|
|
|
| 50 |
ckpt_files = [
|
| 51 |
os.path.join(ckpt_dir, f)
|
| 52 |
for f in sorted(os.listdir(ckpt_dir))
|
| 53 |
+
if f.endswith(('.pt', '.pth'))
|
| 54 |
]
|
| 55 |
except Exception as e:
|
| 56 |
print(f"Error scanning checkpoint directory {ckpt_dir}: {e}")
|
| 57 |
return [], None
|
| 58 |
|
| 59 |
+
|
|
|
|
| 60 |
choices_list = [(os.path.basename(f), f) for f in ckpt_files]
|
| 61 |
|
| 62 |
default_ckpt_path = os.path.join(ckpt_dir, 'mtlora.pt')
|
|
|
|
| 64 |
if default_ckpt_path in ckpt_files:
|
| 65 |
return choices_list, default_ckpt_path
|
| 66 |
elif ckpt_files:
|
| 67 |
+
return choices_list, ckpt_files[0]
|
| 68 |
else:
|
| 69 |
print(f"No checkpoints found in {ckpt_dir}")
|
| 70 |
return [], None
|
|
|
|
| 81 |
return model,transform
|
| 82 |
|
| 83 |
def load_model_and_update_status(model_filepath):
|
| 84 |
+
"""Wrapper function to load a model """
|
|
|
|
|
|
|
|
|
|
| 85 |
global model, current_ckpt_dir
|
| 86 |
|
| 87 |
if model_filepath is None or model_filepath == "":
|
| 88 |
return "No checkpoint selected."
|
| 89 |
|
| 90 |
+
# Check if this model filepath is already loaded
|
| 91 |
if model is not None and model_filepath == current_ckpt_dir:
|
| 92 |
status = f"Model already loaded: {os.path.basename(model_filepath)}"
|
| 93 |
print(status)
|
|
|
|
| 95 |
|
| 96 |
gr.Info(f"Loading model: {os.path.basename(model_filepath)}...")
|
| 97 |
try:
|
| 98 |
+
|
|
|
|
|
|
|
| 99 |
init_model(ckpt_dir=model_filepath, detection_confidence=0.5)
|
|
|
|
| 100 |
|
| 101 |
current_ckpt_dir = model_filepath # Set global path on successful load
|
| 102 |
status = f"Successfully loaded: {os.path.basename(model_filepath)}"
|
|
|
|
| 163 |
return results
|
| 164 |
|
| 165 |
def get_centroid_weighted_age(probs):
|
| 166 |
+
"""
|
| 167 |
+
Using centroids of age group we calculate an age regression number
|
| 168 |
+
using an average weight based on predicted probability distribution
|
| 169 |
+
"""
|
| 170 |
probs = list(probs.values())
|
| 171 |
centroids = [1, 4.5, 14.5, 24.5, 34.5, 44.5, 54.5, 64.5, 80]
|
| 172 |
age = 0
|
| 173 |
+
|
| 174 |
for i,p in enumerate(probs):
|
| 175 |
age += p * centroids[i]
|
| 176 |
|
|
|
|
| 188 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 189 |
print(f"Using device: {device}")
|
| 190 |
|
|
|
|
| 191 |
if not os.path.exists(ckpt_dir):
|
| 192 |
error_msg = f"Model weights not found: {ckpt_dir}."
|
| 193 |
print(f"ERROR: {error_msg}")
|
|
|
|
| 222 |
return None, "<p style='color: red;'>Please upload an image</p>"
|
| 223 |
|
| 224 |
# Ensure model is initialized
|
|
|
|
|
|
|
| 225 |
# this check ensures the selected model is loaded.
|
| 226 |
if model is None or selected_checkpoint_path != current_ckpt_dir:
|
| 227 |
print(f"Model mismatch or not loaded. Selected: {selected_checkpoint_path}, Current: {current_ckpt_dir}")
|
|
|
|
| 231 |
|
| 232 |
|
| 233 |
try:
|
|
|
|
| 234 |
|
| 235 |
# Convert PIL to OpenCV format (BGR) for the detector
|
| 236 |
if isinstance(image, Image.Image):
|
| 237 |
img_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
| 238 |
else:
|
|
|
|
| 239 |
img_cv = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
| 240 |
|
| 241 |
# Create a PIL copy to draw annotations on
|
| 242 |
img_pil_annotated = image.copy()
|
| 243 |
draw = ImageDraw.Draw(img_pil_annotated)
|
| 244 |
|
| 245 |
+
# --- Detect faces ---
|
| 246 |
faces = detector.detect(img_cv, pad_rect=True)
|
| 247 |
|
| 248 |
if faces is None or len(faces) == 0:
|
| 249 |
return image, "<p style='color: orange;'>No faces detected in the image</p>"
|
| 250 |
|
| 251 |
+
# --- Process detected faces ---
|
| 252 |
crops_pil = []
|
| 253 |
face_data = []
|
| 254 |
|
|
|
|
| 257 |
crop_pil = Image.fromarray(crop_rgb)
|
| 258 |
crops_pil.append(crop_pil)
|
| 259 |
|
| 260 |
+
# Resize crop to 336x336 for display, to match model input size
|
| 261 |
crop_resized = crop_pil.resize((336, 336), Image.Resampling.LANCZOS)
|
| 262 |
|
| 263 |
face_data.append({
|
| 264 |
'bbox': bbox,
|
| 265 |
'detection_confidence': float(confidence),
|
| 266 |
+
'crop_image': crop_resized
|
| 267 |
})
|
| 268 |
|
| 269 |
+
# --- Batch transform and predict ---
|
| 270 |
crop_tensors = [transform(crop_pil) for crop_pil in crops_pil]
|
| 271 |
batch_tensor = torch.stack(crop_tensors).to(device)
|
| 272 |
|
|
|
|
| 273 |
predictions = predict(model, batch_tensor)
|
| 274 |
|
| 275 |
# Combine face data with predictions
|
| 276 |
for face, pred in zip(face_data, predictions):
|
| 277 |
face['predictions'] = pred
|
| 278 |
|
| 279 |
+
# --- Create annotated image (using PIL) ---
|
| 280 |
for idx, face in enumerate(face_data):
|
| 281 |
bbox = face['bbox']
|
| 282 |
pred = face['predictions']
|
| 283 |
x, y, w, h = bbox
|
| 284 |
|
| 285 |
+
# --- Calculate Adaptive Font ---
|
| 286 |
font_size_ratio = 0.08
|
| 287 |
min_font_size = 12
|
| 288 |
max_font_size = 48
|
|
|
|
| 314 |
lines_to_draw.append(f"Emotion: {emo_label} ({emo_conf*100:.0f}%)")
|
| 315 |
|
| 316 |
|
| 317 |
+
# --- Calculate total height of the text block ---
|
| 318 |
line_spacing = 10
|
| 319 |
total_text_height = 0
|
| 320 |
for line in lines_to_draw:
|
| 321 |
_left, top, _right, bottom = draw.textbbox((0, 0), line, font=font)
|
| 322 |
total_text_height += (bottom - top) + line_spacing
|
| 323 |
|
| 324 |
+
# --- Place text ABOVE or BELOW the box ---
|
| 325 |
if y - total_text_height > 0:
|
|
|
|
| 326 |
text_y = y - line_spacing
|
| 327 |
for line in reversed(lines_to_draw):
|
| 328 |
left, top, right, bottom = draw.textbbox((x, text_y), line, font=font, anchor="ls") # anchor left-baseline
|
|
|
|
| 330 |
draw.text((x, text_y), line, font=font, fill="white", anchor="ls")
|
| 331 |
text_y = top - line_spacing # Move y-position up for the next line
|
| 332 |
else:
|
|
|
|
| 333 |
text_y = y + h + line_spacing
|
| 334 |
for line in lines_to_draw:
|
| 335 |
left, top, right, bottom = draw.textbbox((x, text_y), line, font=font, anchor="lt")
|
|
|
|
| 337 |
draw.text((x, text_y), line, font=font, fill="white", anchor="lt")
|
| 338 |
text_y = bottom + line_spacing
|
| 339 |
|
|
|
|
| 340 |
|
| 341 |
# Helper function to convert PIL image to base64
|
| 342 |
def pil_to_base64(img_pil):
|
|
|
|
| 345 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
| 346 |
return f"data:image/jpeg;base64,{img_str}"
|
| 347 |
|
|
|
|
| 348 |
results_html = f"""
|
| 349 |
<style>
|
| 350 |
:root {{
|
|
|
|
| 596 |
"""
|
| 597 |
results_html += "</div>"
|
| 598 |
|
| 599 |
+
# --- Return the annotated PIL image and HTML ---
|
| 600 |
return img_pil_annotated, results_html
|
| 601 |
|
| 602 |
except Exception as e:
|
|
|
|
| 618 |
"""
|
| 619 |
|
| 620 |
# Create interface
|
|
|
|
| 621 |
with gr.Blocks(css=custom_css, title="Face Classification System", theme=gr.themes.Default()) as demo:
|
| 622 |
|
| 623 |
with gr.Row():
|
|
|
|
| 628 |
with gr.Column(scale=3):
|
| 629 |
checkpoint_dropdown = gr.Dropdown(
|
| 630 |
label="Select Model Checkpoint",
|
| 631 |
+
choices=checkpoint_list,
|
| 632 |
+
value=default_checkpoint,
|
| 633 |
)
|
| 634 |
with gr.Column(scale=2):
|
| 635 |
model_status_text = gr.Textbox(
|
|
|
|
| 684 |
size="lg"
|
| 685 |
)
|
| 686 |
|
|
|
|
| 687 |
# Dynamically load example images from example directory
|
| 688 |
example_dir = "example"
|
| 689 |
example_images = []
|
|
|
|
| 705 |
cache_examples=False
|
| 706 |
)
|
| 707 |
|
| 708 |
+
# Results section
|
| 709 |
with gr.Row():
|
| 710 |
with gr.Column(scale=1):
|
| 711 |
output_html = gr.HTML(
|
|
|
|
| 716 |
# Event handlers
|
| 717 |
analyze_btn.click(
|
| 718 |
fn=process_image,
|
| 719 |
+
inputs=[input_image, checkpoint_dropdown],
|
| 720 |
outputs=[output_image, output_html]
|
| 721 |
)
|
| 722 |
|
| 723 |
checkpoint_dropdown.change(
|
| 724 |
fn=load_model_and_update_status,
|
| 725 |
+
inputs=[checkpoint_dropdown],
|
| 726 |
outputs=[model_status_text]
|
| 727 |
)
|
| 728 |
|
|
|
|
| 749 |
except Exception as e:
|
| 750 |
print(f"CRITICAL: Failed to download models from Hub. {e}")
|
| 751 |
traceback.print_exc()
|
|
|
|
| 752 |
|
| 753 |
|
|
|
|
| 754 |
# This will now find the files you just downloaded
|
| 755 |
checkpoint_list, default_checkpoint = scan_checkpoints(CHECKPOINTS_DIR)
|
| 756 |
|
|
|
|
| 760 |
print(f"Found checkpoints: {len(checkpoint_list)} file(s).")
|
| 761 |
print(f"Default checkpoint: {default_checkpoint}")
|
| 762 |
|
| 763 |
+
# --- Try to initialize default model ---
|
| 764 |
initial_status_msg = "No default model found. Please select one."
|
| 765 |
if default_checkpoint:
|
| 766 |
print(f"\nInitializing default model: {default_checkpoint}")
|
|
|
|
| 772 |
print("⚠ Warning: No default model to load.")
|
| 773 |
|
| 774 |
|
| 775 |
+
# --- Create interface FIRST (so it shows even if model fails) ---
|
| 776 |
print("Creating Gradio interface...")
|
| 777 |
demo = create_interface(checkpoint_list, default_checkpoint, initial_status_msg)
|
| 778 |
print("✓ Interface created successfully!")
|
|
|
|
| 782 |
import argparse
|
| 783 |
|
| 784 |
parser = argparse.ArgumentParser(description="VLM Soft Biometrics - Gradio Interface")
|
|
|
|
| 785 |
parser.add_argument("--ckpt_dir", type=str, default="./checkpoints/",
|
| 786 |
help="Path to the checkpoint directory (will be populated from HF Hub)")
|
| 787 |
parser.add_argument("--detection_confidence", type=float, default=0.5,
|
|
|
|
| 794 |
help="Server name/IP to bind to")
|
| 795 |
args = parser.parse_args()
|
| 796 |
|
|
|
|
| 797 |
CHECKPOINTS_DIR = args.ckpt_dir
|
|
|
|
| 798 |
|
| 799 |
print(f"\nLaunching server on {args.server_name}:{args.port}")
|
| 800 |
print(f"Monitoring checkpoint directory: {CHECKPOINTS_DIR}")
|
core/LICENSE_PE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
src/LICENSE_MTLORA
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) SCALE Lab, Brown University.
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE
|