File size: 6,141 Bytes
c846193
 
 
 
 
 
 
 
d2c645a
c846193
 
 
 
 
d2c645a
 
 
 
 
 
 
 
 
c846193
 
 
 
 
 
 
c231584
 
c846193
 
 
 
 
 
 
 
c231584
 
 
 
 
 
 
c846193
 
 
 
c231584
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c846193
 
 
 
 
 
 
 
 
 
 
 
 
c231584
c846193
 
 
 
c231584
c846193
 
 
 
c231584
c846193
 
 
 
c231584
c846193
 
 
c231584
c846193
 
 
 
 
c231584
c846193
 
 
c231584
 
 
 
 
 
 
 
c846193
c231584
 
 
c846193
 
 
c231584
c846193
 
 
 
 
 
c231584
c846193
 
c231584
c846193
c231584
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import cv2
import math
import base64
import numpy as np
import mediapipe as mp
from io import BytesIO
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import Response
from fastapi.middleware.cors import CORSMiddleware  # Add CORS support
from PIL import Image

# Initialize FastAPI app
app = FastAPI()

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize Mediapipe Pose model
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(
    static_image_mode=False,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
)

# Function to calculate angles between three points
def calculate_angle(a, b, c):
    ab = (b[0] - a[0], b[1] - a[1])
    bc = (c[0] - b[0], c[1] - b[1])
    
    dot_product = ab[0] * bc[0] + ab[1] * bc[1]
    magnitude_ab = math.sqrt(ab[0]**2 + ab[1]**2)
    magnitude_bc = math.sqrt(bc[0]**2 + bc[1]**2)
    
    # To avoid division by zero
    if magnitude_ab * magnitude_bc == 0:
        return 0.0

    # Clamp the cosine value to the [-1, 1] range to avoid numerical errors
    cosine_angle = max(min(dot_product / (magnitude_ab * magnitude_bc), 1), -1)
    angle_radians = math.acos(cosine_angle)
    angle_degrees = math.degrees(angle_radians)
    
    return angle_degrees

# Function to calculate a simplified REBA score based on trunk (hip) and neck angles.
def calculate_reba(trunk_angle, neck_angle):
    """
    This is a simplified approach:
      - For the trunk (approximated by the hip angle), a nearly upright posture (angle >= 160°) is scored as 1,
        a moderately bent posture (angle between 140° and 160°) is scored as 2, and a severely bent posture (<140°) is scored as 3.
      - Similarly for the neck angle.
      - The REBA score is the sum of these scores.
      - Finally, we define a risk level based on the total score.
    """
    # Determine trunk score (using the hip angle)
    if trunk_angle >= 160:
        trunk_score = 1
    elif trunk_angle >= 140:
        trunk_score = 2
    else:
        trunk_score = 3

    # Determine neck score
    if neck_angle >= 150:
        neck_score = 1
    elif neck_angle >= 130:
        neck_score = 2
    else:
        neck_score = 3

    # Simplified REBA group A score (normally REBA also considers legs, arms, load, etc.)
    reba_score = trunk_score + neck_score

    # Define risk levels based on the score
    if reba_score <= 2:
        risk = "Negligible"
    elif reba_score <= 4:
        risk = "Low"
    elif reba_score <= 6:
        risk = "Medium"
    else:
        risk = "High"

    return reba_score, risk

# Process image with Mediapipe Pose Estimation and analyze posture using REBA score
def process_frame(image):
    h, w, _ = image.shape
    
    # Convert to RGB
    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    image_rgb.flags.writeable = False
    results = pose.process(image_rgb)
    image_rgb.flags.writeable = True
    
    # Convert back to BGR for display
    image = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2BGR)
    
    if results.pose_landmarks:
        # Get key landmarks from the right side
        right_shoulder = results.pose_landmarks.landmark[mp_pose.PoseLandmark.RIGHT_SHOULDER]
        right_hip = results.pose_landmarks.landmark[mp_pose.PoseLandmark.RIGHT_HIP]
        right_ear = results.pose_landmarks.landmark[mp_pose.PoseLandmark.RIGHT_EAR]
        
        # Convert normalized coordinates to pixel coordinates
        cx_rs, cy_rs = int(right_shoulder.x * w), int(right_shoulder.y * h)
        cx_rh, cy_rh = int(right_hip.x * w), int(right_hip.y * h)
        cx_re, cy_re = int(right_ear.x * w), int(right_ear.y * h)
        
        # Create reference points by applying an offset (helps approximate vertical)
        offset = 60
        upper_shoulder = (cx_rs, max(0, cy_rs - offset))
        upper_hip = (cx_rh, max(0, cy_rh - offset))
        
        # Draw reference landmarks on the image
        cv2.circle(image, upper_shoulder, 5, (0, 255, 0), -1)
        cv2.circle(image, upper_hip, 5, (0, 255, 0), -1)
        
        # Draw lines connecting key points
        cv2.line(image, (cx_rh, cy_rh), (cx_rs, cy_rs), (255, 0, 255), 2)  # Hip to shoulder
        cv2.line(image, (cx_rs, cy_rs), (cx_re, cy_re), (255, 255, 0), 2)  # Shoulder to ear
        cv2.line(image, (cx_rh, cy_rh), upper_hip, (0, 165, 255), 2)       # Hip to upper hip
        cv2.line(image, (cx_rs, cy_rs), upper_shoulder, (0, 255, 255), 2)  # Shoulder to upper shoulder
        
        # Calculate angles using the defined reference points
        angle_hip = calculate_angle(upper_hip, (cx_rh, cy_rh), (cx_rs, cy_rs))
        angle_neck = calculate_angle((cx_rs, cy_rs), (cx_re, cy_re), upper_shoulder)
        
        # Compute the simplified REBA score and corresponding risk level
        reba_score, risk = calculate_reba(angle_hip, angle_neck)
        
        # Display the calculated angles on the image
        cv2.putText(image, f"Hip Angle: {angle_hip:.1f}", (10, 60), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 255), 2)
        cv2.putText(image, f"Neck Angle: {angle_neck:.1f}", (10, 90), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
        
        # Display the simplified REBA score and risk level on the image
        cv2.putText(image, f"REBA Score: {reba_score} ({risk})", (10, 120), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)

    return image

# API Route to receive an image and return the processed image with REBA analysis
@app.post("/upload")
async def upload_image(file: UploadFile = File(...)):
    contents = await file.read()
    image = Image.open(BytesIO(contents))
    image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
    
    # Process the image (the processing function now includes REBA score analysis)
    processed_image = process_frame(image)
    
    # Encode the processed image to return it as JPEG
    _, buffer = cv2.imencode(".jpg", processed_image)
    return Response(content=buffer.tobytes(), media_type="image/jpeg")