Spaces:
Running
Running
import gradio as gr | |
from transformers import CLIPProcessor, CLIPModel | |
from PIL import Image | |
import base64 | |
import json | |
import requests | |
from io import BytesIO | |
import os | |
import time | |
from openai import OpenAI | |
# Constants | |
API_URL = "https://api-inference.huggingface.co/models/google/siglip-so400m-patch14-384" | |
# Fetch the Hugging Face API token from environment variables | |
HF_API_TOKEN = os.getenv("HF_API_TOKEN") | |
if HF_API_TOKEN is None: | |
raise ValueError( | |
"Hugging Face API token is not set. Please add it as an environment variable." | |
) | |
headers = {"Authorization": f"Bearer {HF_API_TOKEN}"} | |
# BMI model classes | |
model1_classes = [ | |
"underweight (x < 18.5 BMI)", | |
"normal weight (18.5 < x < 25 BMI)", | |
"overweight (25 BMI < x < 30)", | |
"obesity (x > 30 BMI)", | |
] | |
model2_classes = ["MALE", "FEMALE"] | |
# Define the default BMI classes and ranges | |
bmi_classes_ranges = { | |
"underweight (x < 18.5 bmi)": [16.0, 18.5], | |
"normal weight (18.5 < x < 25 bmi)": [18.5, 25], | |
"overweight (25 bmi < x < 30)": [25, 30], | |
"obesity (x > 30 bmi)": [30, 40], | |
} | |
# Function to convert image to base64 | |
def encode_image_to_base64(image): | |
buffered = BytesIO() | |
image.save(buffered, format="JPEG") | |
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") | |
return img_str | |
# Function to call the Hugging Face API | |
def query(data, max_retries=5, wait_time=10): | |
payload = {"parameters": data["parameters"], "inputs": data["image_base64"]} | |
for attempt in range(max_retries): | |
response = requests.post(API_URL, headers=headers, json=payload) | |
print("Raw response from API:", response, response.text, headers) | |
# Check if the model is still loading | |
if response.status_code == 503: | |
print(f"Model is still loading. Retrying in {wait_time} seconds...") | |
time.sleep(wait_time) # Wait before retrying | |
continue | |
try: | |
return response.json() # Ensure we are parsing JSON response | |
except ValueError as e: | |
print(f"Error parsing response as JSON: {e}") | |
return None | |
raise ValueError("Failed to get a valid response after multiple retries.") | |
def query2(data): | |
payload = {"parameters": data["parameters"], "inputs": data["image_base64"]} | |
response = requests.post(API_URL, headers=headers, json=payload) | |
# Print the raw response to inspect it | |
print("Raw response from API:", response,response.text,headers) | |
try: | |
return response.json() # Ensure we are parsing JSON response | |
except ValueError as e: | |
print(f"Error parsing response as JSON: {e}") | |
return None | |
def get_bmi_classes_range(predicted_bmi_class, bmi_range): | |
# Determine the specific BMI range based on the output | |
lower_bound, upper_bound = bmi_classes_ranges[predicted_bmi_class.lower()] | |
if "BMI <=" in bmi_range: | |
value = float(bmi_range.split("<=")[1].strip()) | |
return [lower_bound, value] | |
elif "BMI >" in bmi_range: | |
value = float(bmi_range.split(">")[1].strip()) | |
return [value, upper_bound] | |
else: | |
return [] | |
def chatgpt_openai(height="", gender="", bmi_class_model1="", age=""): | |
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") | |
content = f"""I have a person {gender} who is {age} and classified as {bmi_class_model1}. | |
Can you suggest a reasonable range of BMI values that typically represent people in this category? | |
Do not include any explanations, only provide an RFC8259 compliant JSON response following this format without deviation. | |
Output JSON in this format: | |
{{ | |
"bmi_range": {{ | |
"min": 10.0, | |
"max": 18.4 | |
}} | |
}}""" | |
try: | |
client = OpenAI() | |
response = client.chat.completions.create( | |
model="gpt-4", | |
messages=[ | |
{"role": "system", "content": "You are a health expert specializing in analyzing patient information, including gender, height, and BMI classification, to provide data-driven health insights based on USA statistics."}, | |
{"role": "user", "content": content}, | |
], | |
) | |
except Exception as e: | |
print(f"Error calling OpenAI API: {e}") | |
raise ValueError("Failed to connect to OpenAI API") | |
# Print the entire response to understand its structure | |
print("Raw response from OpenAI:", response) | |
# Check if the response has 'choices' and it's a list with at least one element | |
if not hasattr(response, "choices") or len(response.choices) == 0: | |
raise ValueError("Invalid response structure from OpenAI API") | |
# Extract the content from the first choice | |
message_content = response.choices[0].message.content | |
print("Response content to be parsed:", message_content) | |
# Parse the content as JSON | |
try: | |
json_object = json.loads(message_content.strip()) | |
except json.JSONDecodeError as e: | |
print(f"JSON Decode Error: {e}") | |
raise ValueError("Failed to parse JSON response from OpenAI API") | |
return json_object | |
def chatgpt_openai2(height="", gender="", bmi_class_model1="", age=""): | |
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") | |
content = f"""I have a person {gender} who is {height} and classified as {bmi_class_model1}. | |
Can you suggest a reasonable range of BMI values that typically represent people who are {bmi_class_model1}. | |
Output JSON in this format: | |
{{ | |
"bmi_range": {{ | |
"min": 24.3, | |
"max": 28.9 | |
}} | |
}}""" | |
client = OpenAI() | |
completion = client.chat.completions.create( | |
model="gpt-4o", | |
messages=[ | |
{ | |
"role": "system", | |
"content": "You are a health expert specializing in analyzing patient information, including gender, height, and BMI classification, to provide data-driven health insights based on USA statistics.", | |
}, | |
{"role": "user", "content": content}, | |
], | |
) | |
print(content,completion) | |
json_object = json.loads(completion.choices[0].message.content) | |
return json_object | |
def predict_bmi(image, height_in_inches, age): | |
image_base64 = encode_image_to_base64(image) | |
request_payload = { | |
"image_base64": image_base64, | |
"parameters": {"candidate_labels": model1_classes}, | |
} | |
print("res ",request_payload) | |
# API call to get the model1 output | |
output = query(request_payload) | |
# Check if the output is valid and not None | |
if not output or not isinstance(output, list): | |
raise ValueError("Invalid response from API: Expected a list of predictions") | |
# Process the model1 output (ensure output is a list of dictionaries) | |
try: | |
predicted_class = max(output, key=lambda x: x["score"])["label"] | |
except (KeyError, TypeError) as e: | |
print(f"Error processing output: {e}") | |
raise ValueError("Unexpected API response format") | |
request_payload2 = { | |
"image_base64": image_base64, | |
"parameters": {"candidate_labels": model2_classes}, | |
} | |
# API call to get the model2 output | |
output2 = query(request_payload2) | |
# Check if the output is valid and not None | |
if not output2 or not isinstance(output2, list): | |
raise ValueError( | |
"Invalid response from API: Expected a list of predictions model2" | |
) | |
# Process the model2 output2 (ensure output is a list of dictionaries) | |
try: | |
predicted_class2 = max(output2, key=lambda x: x["score"])["label"] | |
except (KeyError, TypeError) as e: | |
print(f"Error processing output2: {e}") | |
raise ValueError("Unexpected API response format") | |
bmi_range = bmi_classes_ranges[predicted_class.lower()] | |
if "normal weight" in predicted_class or "overweight" in predicted_class: | |
# Calculate the average BMI | |
average_bmi = (bmi_range[0] + bmi_range[1]) / 2 | |
model3_classes = [f"BMI <= {average_bmi:.1f}", f"BMI > {average_bmi:.1f}"] | |
request_payload3 = { | |
"image_base64": image_base64, | |
"parameters": {"candidate_labels": model3_classes}, | |
} | |
# API call to get the model3 output | |
output3 = query(request_payload3) | |
# Check if the output is valid and not None | |
if not output3 or not isinstance(output3, list): | |
raise ValueError( | |
"Invalid response from API: Expected a list of predictions3" | |
) | |
# Process the model3 output (ensure output is a list of dictionaries) | |
try: | |
predicted_class3 = max(output3, key=lambda x: x["score"])["label"] | |
except (KeyError, TypeError) as e: | |
print(f"Error processing output3: {e}") | |
raise ValueError("Unexpected API response format") | |
get_bmi_range = get_bmi_classes_range(predicted_class, predicted_class3) | |
else: | |
model3_gpt = chatgpt_openai( | |
height_in_inches, predicted_class2, predicted_class, age | |
) | |
print(model3_gpt,'gpt') | |
data = model3_gpt["bmi_range"] | |
get_bmi_range = [data["min"], data["max"]] | |
weight_min, weight_max = get_weight_range( | |
get_bmi_range[0], get_bmi_range[1], height_in_inches | |
) | |
print(f"Predicted weight range: {weight_min:.2f} lbs - {weight_max:.2f} lbs") | |
result = { | |
"weightCategory": f"{predicted_class}", | |
"height": str(height_in_inches), | |
"bmi_range": get_bmi_range, | |
"predictedWeightRange": f"{weight_min:.2f} lbs - {weight_max:.2f} lbs", | |
} | |
return result | |
def get_weight_range(bmi_min, bmi_max, height): | |
# BMI formula: BMI = weight (lb) / [height (in)]^2 * 703 | |
# Rearranged to find weight: weight = BMI * height^2 / 703 | |
weight_min = (bmi_min * height**2) / 703 | |
weight_max = (bmi_max * height**2) / 703 | |
return weight_min, weight_max | |
# Create Gradio interface with updated input components | |
interface = gr.Interface( | |
fn=predict_bmi, | |
inputs=[ | |
gr.Image(type="pil"), | |
gr.Number(label="Height in inches"), | |
gr.Number(label="Age"), | |
], | |
outputs="json", | |
title="BMI Prediction", | |
description="Upload an image and enter your height to predict BMI category and receive a detailed prediction.", | |
) | |
interface.launch() | |