Spaces:
Sleeping
Sleeping
# single_person_processor.py | |
import os | |
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # Suppress TensorFlow warnings | |
import io | |
import logging | |
import numpy as np | |
import tensorflow as tf | |
from rembg import new_session, remove | |
from PIL import Image | |
from typing import Tuple, Dict | |
from tensorflow.keras.preprocessing.image import img_to_array | |
# Configure logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
class ImagePreprocessor: | |
"""Class to preprocess images by removing background.""" | |
def __init__(self, img_size: Tuple[int, int] = (128, 128)): | |
""" | |
Initialize the image preprocessor. | |
Parameters: | |
- img_size: Target size for images (default is (128, 128)). | |
""" | |
self.img_size = img_size | |
self.session = new_session() # Create a new session for rembg | |
logging.info("Image processor initialized.") | |
def process_single_person(self, front_path: str, side_path: str) -> Tuple[np.ndarray, np.ndarray]: | |
""" | |
Process two images (front and side views). | |
Parameters: | |
- front_path: Path to the front image. | |
- side_path: Path to the side image. | |
Returns: | |
- A tuple containing processed front and side images as NumPy arrays. | |
""" | |
return ( | |
self._process_image(front_path), | |
self._process_image(side_path) | |
) | |
def _process_image(self, image_path: str) -> np.ndarray: | |
""" | |
Remove background and process a single image. | |
Parameters: | |
- image_path: Path to the image file. | |
Returns: | |
- Processed image as a NumPy array. | |
""" | |
try: | |
with open(image_path, "rb") as f: | |
img_bytes = f.read() | |
# Remove background using rembg | |
bg_removed = Image.open(io.BytesIO(remove(img_bytes, session=self.session))) | |
# Convert person to white and background to black | |
white_img = Image.new("RGBA", bg_removed.size, (255, 255, 255, 255)) | |
white_img.putalpha(bg_removed.getchannel('A')) | |
final_img = Image.alpha_composite( | |
Image.new("RGBA", white_img.size, (0, 0, 0, 255)), | |
white_img | |
) | |
# Prepare the image for the model | |
return self._prepare_image(final_img) | |
except Exception as e: | |
logging.error(f"Failed to process {image_path}: {str(e)}") | |
raise | |
def _prepare_image(self, image: Image.Image) -> np.ndarray: | |
""" | |
Convert the image to the model's input format. | |
Parameters: | |
- image: PIL Image object. | |
Returns: | |
- Image as a normalized NumPy array. | |
""" | |
return img_to_array( | |
image.convert('L').resize(self.img_size) | |
).astype(np.float32) / 255.0 | |
class SinglePersonPredictor: | |
"""Class to predict body measurements and clothing sizes.""" | |
MEASUREMENT_INDICES = { | |
'ankle': 0, 'arm-length': 1, 'bicep': 2, 'calf': 3, | |
'chest': 4, 'forearm': 5, 'height': 6, 'hip': 7, | |
'leg-length': 8, 'shoulder-breadth': 9, | |
'shoulder-to-crotch': 10, 'thigh': 11, 'waist': 12, 'wrist': 13 | |
} | |
SIZE_CHARTS = { | |
'male': { | |
'tshirt': [ | |
(97, 42, 'S'), (104, 45, 'M'), (112, 48, 'L'), | |
(120, 51, 'XL'), (128, 54, 'XXL'), (136, 57, 'XXXL') | |
], | |
'pants': [ | |
(76, 102, 30), (81, 107, 32), (86, 112, 34), | |
(91, 117, 36), (97, 122, 38), (102, 127, 40), (107, 132, 42) | |
] | |
}, | |
'female': { | |
'tshirt': [ | |
(89, 38, 'S'), (96, 41, 'M'), (104, 44, 'L'), | |
(112, 47, 'XL'), (120, 50, 'XXL'), (128, 53, 'XXXL') | |
], | |
'pants': [ | |
(66, 92, 26), (71, 97, 28), (76, 102, 30), | |
(81, 107, 32), (86, 112, 34), (91, 117, 36), (97, 122, 38) | |
] | |
} | |
} | |
def __init__(self, model_path: str = 'best_model.keras'): | |
""" | |
Initialize the predictor. | |
Parameters: | |
- model_path: Path to the trained model. | |
""" | |
self.model = tf.keras.models.load_model(model_path) | |
self.preprocessor = ImagePreprocessor() | |
logging.info("Model loaded successfully.") | |
def predict_measurements(self, | |
front_img_path: str, | |
side_img_path: str, | |
gender: int, | |
height_cm: float, | |
weight_kg: float, | |
apparel_type: str = "all") -> Dict: | |
""" | |
Perform predictions for a single person and calculate clothing sizes. | |
Parameters: | |
- front_img_path: Path to the front image. | |
- side_img_path: Path to the side image. | |
- gender: 0 for male, 1 for female. | |
- height_cm: Height in centimeters. | |
- weight_kg: Weight in kilograms. | |
- apparel_type: Specify "tshirt", "pants", or "all". | |
Returns: | |
- Dictionary containing predicted measurements and clothing sizes. | |
""" | |
try: | |
# Validate inputs | |
if gender not in (0, 1): | |
raise ValueError("Gender must be 0 (male) or 1 (female).") | |
if not 100 <= height_cm <= 250: | |
raise ValueError("Height must be between 100-250 cm.") | |
if not 30 <= weight_kg <= 300: | |
raise ValueError("Weight must be between 30-300 kg.") | |
if apparel_type not in ["tshirt", "pants", "all"]: | |
raise ValueError("Apparel type must be 'tshirt', 'pants', or 'all'.") | |
# Process images | |
front_arr, side_arr = self.preprocessor.process_single_person(front_img_path, side_img_path) | |
meta_arr = np.array([[gender, height_cm, weight_kg]], dtype=np.float32) | |
# Make predictions | |
prediction = self.model.predict([ | |
np.expand_dims(front_arr, axis=0), | |
np.expand_dims(side_arr, axis=0), | |
meta_arr | |
]) | |
# Convert predictions to dictionary | |
measurements = { | |
name: round(float(prediction[0][idx]), 2) | |
for name, idx in self.MEASUREMENT_INDICES.items() | |
} | |
result = {} | |
if apparel_type == "tshirt": | |
result["tshirt_size"] = self.calculate_tshirt_size(gender, measurements, weight_kg) | |
elif apparel_type == "pants": | |
result["pants_size"] = self.calculate_pants_size(gender, measurements, weight_kg) | |
elif apparel_type == "all": | |
result["body_measurements"] = measurements | |
tshirt_size, pants_size = self.calculate_apparel_size(gender, measurements, weight_kg) | |
result["tshirt_size"] = tshirt_size | |
result["pants_size"] = pants_size | |
return result | |
except Exception as e: | |
logging.error(f"Prediction failed: {str(e)}") | |
raise | |
def calculate_tshirt_size(self, gender: int, measurements: Dict, weight: float) -> str: | |
""" | |
Calculate t-shirt size based on chest and shoulder-breadth measurements. | |
Parameters: | |
- gender: 0 for male, 1 for female. | |
- measurements: Dictionary of body measurements. | |
- weight: Weight in kilograms. | |
Returns: | |
- T-Shirt size as a string. | |
""" | |
gender_str = 'male' if gender == 0 else 'female' | |
chart = self.SIZE_CHARTS[gender_str]['tshirt'] | |
chest = measurements['chest'] | |
shoulder_breadth = measurements['shoulder-breadth'] | |
base_size = next( | |
(size for max_chest, max_shoulder, size in chart | |
if chest <= max_chest and shoulder_breadth <= max_shoulder), | |
'XXXL' | |
) | |
if weight > 95 and gender == 0 or weight > 80 and gender == 1: | |
base_size = 'XXL' | |
if measurements['height'] > 180: | |
base_size = f"Tall {base_size}" | |
return base_size | |
def calculate_pants_size(self, gender: int, measurements: Dict, weight: float) -> int: | |
""" | |
Calculate pants size based on waist and hip measurements. | |
Parameters: | |
- gender: 0 for male, 1 for female. | |
- measurements: Dictionary of body measurements. | |
- weight: Weight in kilograms. | |
Returns: | |
- Pants size as an integer. | |
""" | |
gender_str = 'male' if gender == 0 else 'female' | |
chart = self.SIZE_CHARTS[gender_str]['pants'] | |
waist = measurements['waist'] | |
hip = measurements['hip'] | |
base_size = next( | |
(size for max_waist, max_hip, size in chart | |
if waist <= max_waist and hip <= max_hip), | |
chart[-1][2] | |
) | |
if measurements['height'] > 180 and gender == 0: | |
base_size += 2 if base_size < 40 else 0 | |
return base_size | |
def calculate_apparel_size(self, gender: int, measurements: Dict, weight: float) -> Tuple[str, int]: | |
""" | |
Calculate both t-shirt and pants sizes. | |
Parameters: | |
- gender: 0 for male, 1 for female. | |
- measurements: Dictionary of body measurements. | |
- weight: Weight in kilograms. | |
Returns: | |
- A tuple containing t-shirt size and pants size. | |
""" | |
tshirt_size = self.calculate_tshirt_size(gender, measurements, weight) | |
pants_size = self.calculate_pants_size(gender, measurements, weight) | |
return tshirt_size, pants_size |