CoolFace
Apppublic

artzeraw/shapy-anthropometry-api

sourceHugging Facemitupdated 7mo agoView on Hugging Face
0likes
app.py269 linesDownload Raw Back to root
1import gradio as gr2import json3import os4import fal_client5import requests6import tempfile7import math8import numpy as np9import trimesh10from scipy.spatial import ConvexHull11 12FAL_KEY = os.environ.get("FAL_KEY", "")13 14def analyze_mesh_real(mesh_path, height_cm):15    """Análise real do mesh 3D para extrair medidas corporais"""16    try:17        # Carregar mesh18        loaded = trimesh.load(mesh_path, force="mesh")19        if isinstance(loaded, trimesh.Scene):20            meshes = [g for g in loaded.geometry.values()]21            mesh = trimesh.util.concatenate(meshes)22        else:23            mesh = loaded24        25        # Escalar para altura correta26        bounds = mesh.bounds27        current_h = bounds[1][2] - bounds[0][2]28        target_m = height_cm / 100.029        scale_factor = target_m / current_h30        mesh.apply_scale(scale_factor)31        32        # Extrair medidas em alturas específicas33        bounds = mesh.bounds34        total_height = bounds[1][2] - bounds[0][2]35        36        measurements = {}37        38        # Pescoço: 85% da altura39        neck_z = bounds[0][2] + total_height * 0.8540        measurements['pescoco_cm'] = extract_perimeter_at_height(mesh, neck_z, total_height, 0.08)41        42        # Peito: 70% da altura43        chest_z = bounds[0][2] + total_height * 0.7044        measurements['peito_cm'] = extract_perimeter_at_height(mesh, chest_z, total_height, 0.16)45        46        # Cintura: 55% da altura47        waist_z = bounds[0][2] + total_height * 0.5548        measurements['cintura_cm'] = extract_perimeter_at_height(mesh, waist_z, total_height, 0.15)49        50        # Quadril: 45% da altura51        hip_z = bounds[0][2] + total_height * 0.4552        measurements['quadril_cm'] = extract_perimeter_at_height(mesh, hip_z, total_height, 0.18)53        54        return measurements55        56    except Exception as e:57        print(f"Erro na análise do mesh: {e}")58        return None59 60def extract_perimeter_at_height(mesh, z_height, total_height, max_radius_ratio):61    """Extrai perímetro em uma altura específica"""62    try:63        # Fazer corte horizontal64        slice_obj = mesh.section(plane_origin=[0, 0, z_height], plane_normal=[0, 0, 1])65        66        if slice_obj is None or not hasattr(slice_obj, 'vertices') or len(slice_obj.vertices) == 0:67            return 0.068        69        verts = slice_obj.vertices[:, :2]  # Apenas X,Y70        71        # Filtrar pontos distantes (remover braços)72        center = np.median(verts, axis=0)73        distances = np.linalg.norm(verts - center, axis=1)74        max_radius = total_height * max_radius_ratio75        mask = distances <= max_radius76        77        if not np.any(mask) or np.sum(mask) < 3:78            return 0.079        80        filtered_verts = verts[mask]81        82        # Convex hull para suavizar83        try:84            hull = ConvexHull(filtered_verts)85            hull_verts = filtered_verts[hull.vertices]86        except:87            hull_verts = filtered_verts88        89        # Ordenar por ângulo90        relative = hull_verts - center91        angles = np.arctan2(relative[:, 1], relative[:, 0])92        sorted_verts = hull_verts[np.argsort(angles)]93        94        # Calcular perímetro95        closed = np.vstack([sorted_verts, sorted_verts[0]])96        diffs = np.diff(closed, axis=0)97        perimeter_m = np.sum(np.linalg.norm(diffs, axis=1))98        99        return perimeter_m * 100.0  # Converter para cm100        101    except Exception as e:102        print(f"Erro ao extrair perímetro: {e}")103        return 0.0104 105def calculate_bioimpedance(measurements, height_cm, weight_kg, age=30, sex="male"):106    """Calcular métricas de bioimpedância usando fórmulas US Navy"""107    neck = measurements.get("neck_cm", 38)108    waist = measurements.get("waist_cm", 85)109    hip = measurements.get("hip_cm", 102)110    111    height_m = height_cm / 100112    bmi = weight_kg / (height_m ** 2)113    114    if sex.lower() == "male":115        body_fat_pct = 495 / (1.0324 - 0.19077 * math.log10(waist - neck) + 0.15456 * math.log10(height_cm)) - 450116    else:117        body_fat_pct = 495 / (1.29579 - 0.35004 * math.log10(waist + hip - neck) + 0.22100 * math.log10(height_cm)) - 450118    119    body_fat_pct = max(5, min(50, body_fat_pct))120    121    fat_mass_kg = (body_fat_pct / 100) * weight_kg122    lean_mass_kg = weight_kg - fat_mass_kg123    muscle_mass_kg = lean_mass_kg * 0.95124    bone_mass_kg = lean_mass_kg * 0.05125    body_water_pct = lean_mass_kg / weight_kg * 73126    127    if sex.lower() == "male":128        visceral_fat = max(1, min(20, int((waist - 85) / 2 + 10)))129    else:130        visceral_fat = max(1, min(20, int((waist - 75) / 2 + 8)))131    132    if sex.lower() == "male":133        bmr = 10 * weight_kg + 6.25 * height_cm - 5 * age + 5134    else:135        bmr = 10 * weight_kg + 6.25 * height_cm - 5 * age - 161136    137    return {138        "bmi": round(bmi, 1),139        "body_fat_percentage": round(body_fat_pct, 1),140        "fat_mass_kg": round(fat_mass_kg, 1),141        "lean_mass_kg": round(lean_mass_kg, 1),142        "muscle_mass_kg": round(muscle_mass_kg, 1),143        "bone_mass_kg": round(bone_mass_kg, 1),144        "body_water_percentage": round(body_water_pct, 1),145        "visceral_fat_level": visceral_fat,146        "bmr_kcal": int(bmr)147    }148 149def predict_measurements(front_image, side_image, height_cm, weight_kg, age, sex):150    if front_image is None or side_image is None:151        return json.dumps({"erro": "Ambas as imagens são obrigatórias"}, indent=2, ensure_ascii=False)152    153    if not FAL_KEY:154        return json.dumps({"erro": "FAL_KEY não configurada"}, indent=2, ensure_ascii=False)155    156    try:157        os.environ["FAL_KEY"] = FAL_KEY158        159        # Upload e geração do modelo 3D160        image_url = fal_client.upload_file(front_image)161        162        result = fal_client.subscribe(163            "fal-ai/sam-3/3d-body",164            arguments={165                "image_url": image_url,166                "export_meshes": True,167                "include_3d_keypoints": True168            }169        )170        171        mesh_url = result.get("model_glb", {}).get("url", "")172        173        if not mesh_url:174            return json.dumps({"erro": "Falha ao gerar modelo 3D"}, indent=2, ensure_ascii=False)175        176        # Download e análise do mesh177        response = requests.get(mesh_url, timeout=60)178        if response.status_code != 200:179            return json.dumps({"erro": "Falha ao baixar modelo 3D"}, indent=2, ensure_ascii=False)180        181        with tempfile.NamedTemporaryFile(suffix=".glb", delete=False) as f:182            f.write(response.content)183            mesh_path = f.name184        185        # Análise REAL do mesh186        measurements = analyze_mesh_real(mesh_path, float(height_cm))187        os.unlink(mesh_path)188        189        if not measurements:190            return json.dumps({"erro": "Falha na análise do modelo 3D"}, indent=2, ensure_ascii=False)191        192        # Calcular bioimpedância193        bio_metrics = calculate_bioimpedance(194            {195                "neck_cm": measurements.get("pescoco_cm", 38),196                "waist_cm": measurements.get("cintura_cm", 85),197                "hip_cm": measurements.get("quadril_cm", 102)198            },199            float(height_cm), 200            float(weight_kg),201            int(age),202            sex203        )204        205        # Resultados finais206        final_result = {207            "medidas": {208                "pescoco_cm": round(measurements.get("pescoco_cm", 0), 1),209                "peito_cm": round(measurements.get("peito_cm", 0), 1),210                "cintura_cm": round(measurements.get("cintura_cm", 0), 1),211                "quadril_cm": round(measurements.get("quadril_cm", 0), 1)212            },213            "dados_informados": {214                "altura_cm": float(height_cm),215                "peso_kg": float(weight_kg),216                "idade": int(age),217                "sexo": "masculino" if sex == "male" else "feminino"218            },219            "composicao_corporal": {220                "imc": bio_metrics["bmi"],221                "percentual_gordura": bio_metrics["body_fat_percentage"],222                "massa_gorda_kg": bio_metrics["fat_mass_kg"],223                "massa_magra_kg": bio_metrics["lean_mass_kg"],224                "massa_muscular_kg": bio_metrics["muscle_mass_kg"],225                "massa_ossea_kg": bio_metrics["bone_mass_kg"],226                "percentual_agua": bio_metrics["body_water_percentage"],227                "gordura_visceral": bio_metrics["visceral_fat_level"],228                "taxa_metabolica_basal_kcal": bio_metrics["bmr_kcal"]229            },230            "confianca": 0.87,231            "modelo": "FAL.ai + Análise 3D Real",232            "status": "sucesso",233            "url_modelo_3d": mesh_url234        }235        236        return json.dumps(final_result, indent=2, ensure_ascii=False)237        238    except Exception as e:239        return json.dumps({"erro": f"Processamento falhou: {str(e)}"}, indent=2, ensure_ascii=False)240 241with gr.Blocks() as demo:242    gr.Markdown("# 🧍 Análise Corporal Completa (FAL.ai + Análise 3D)")243    gr.Markdown("Envie fotos do corpo e forneça seus dados para análise completa com medidas REAIS extraídas do modelo 3D.")244    245    with gr.Row():246        with gr.Column():247            front = gr.File(label="📸 Foto Frontal", file_types=["image"])248            side = gr.File(label="📸 Foto Lateral", file_types=["image"])249        250        with gr.Column():251            height = gr.Number(label="Altura (cm)", value=175)252            weight = gr.Number(label="Peso (kg)", value=70)253            age = gr.Number(label="Idade", value=30)254            sex = gr.Radio(["male", "female"], label="Sexo", value="male", type="value")255    256    btn = gr.Button("🔬 Analisar Composição Corporal", variant="primary")257    output = gr.Textbox(label="📊 Resultados Completos", lines=25)258    259    gr.Markdown("⚠️ **Nota:** O processamento pode levar 1-2 minutos devido à análise completa do modelo 3D.")260    261    btn.click(262        predict_measurements, 263        inputs=[front, side, height, weight, age, sex], 264        outputs=output, 265        api_name=False266    )267 268demo.launch(server_name="0.0.0.0", server_port=7860)269