Spaces:
Runtime error
Runtime error
เพิ่มตรวจจับเอียง test 001
Browse files
app.py
CHANGED
|
@@ -49,6 +49,44 @@ def getMasks(landmarks, h, w):
|
|
| 49 |
return RL_mask, LL_mask, H_mask
|
| 50 |
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
def drawOnTop(img, landmarks, original_shape):
|
| 53 |
h, w = original_shape
|
| 54 |
output = getDenseMask(landmarks, h, w)
|
|
@@ -62,6 +100,26 @@ def drawOnTop(img, landmarks, original_shape):
|
|
| 62 |
|
| 63 |
RL, LL, H = landmarks[0:44], landmarks[44:94], landmarks[94:]
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
# Draw the landmarks as dots
|
| 66 |
for l in RL:
|
| 67 |
image = cv2.circle(image, (int(l[0]), int(l[1])), 5, (1, 0, 1), -1)
|
|
@@ -70,11 +128,11 @@ def drawOnTop(img, landmarks, original_shape):
|
|
| 70 |
for l in H:
|
| 71 |
image = cv2.circle(image, (int(l[0]), int(l[1])), 5, (1, 1, 0), -1)
|
| 72 |
|
| 73 |
-
# Draw horizontal lines for CTR calculation
|
| 74 |
-
# Heart (red line)
|
| 75 |
-
heart_xmin = int(np.min(
|
| 76 |
-
heart_xmax = int(np.max(
|
| 77 |
-
heart_y = int(np.mean([
|
| 78 |
image = cv2.line(image, (heart_xmin, heart_y), (heart_xmax, heart_y), (1, 0, 0), 2)
|
| 79 |
|
| 80 |
# Add vertical lines at heart endpoints to verify widest points
|
|
@@ -82,24 +140,27 @@ def drawOnTop(img, landmarks, original_shape):
|
|
| 82 |
image = cv2.line(image, (heart_xmin, heart_y - line_length), (heart_xmin, heart_y + line_length), (1, 0, 0), 2)
|
| 83 |
image = cv2.line(image, (heart_xmax, heart_y - line_length), (heart_xmax, heart_y + line_length), (1, 0, 0), 2)
|
| 84 |
|
| 85 |
-
# Thorax (blue line)
|
| 86 |
-
thorax_xmin = int(min(np.min(
|
| 87 |
-
thorax_xmax = int(max(np.max(
|
| 88 |
# Find y at leftmost and rightmost points
|
| 89 |
-
if np.min(
|
| 90 |
-
thorax_ymin =
|
| 91 |
else:
|
| 92 |
-
thorax_ymin =
|
| 93 |
-
if np.max(
|
| 94 |
-
thorax_ymax =
|
| 95 |
else:
|
| 96 |
-
thorax_ymax =
|
| 97 |
thorax_y = int(np.mean([thorax_ymin, thorax_ymax]))
|
| 98 |
image = cv2.line(image, (thorax_xmin, thorax_y), (thorax_xmax, thorax_y), (0, 0, 1), 2)
|
| 99 |
|
| 100 |
# Add vertical lines at thorax endpoints to verify widest points
|
| 101 |
image = cv2.line(image, (thorax_xmin, thorax_y - line_length), (thorax_xmin, thorax_y + line_length), (0, 0, 1), 2)
|
| 102 |
image = cv2.line(image, (thorax_xmax, thorax_y - line_length), (thorax_xmax, thorax_y + line_length), (0, 0, 1), 2)
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
return image
|
| 105 |
|
|
@@ -186,14 +247,19 @@ def removePreprocess(output, info):
|
|
| 186 |
return output
|
| 187 |
|
| 188 |
|
| 189 |
-
def calculate_ctr(landmarks):
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
cardiac_width = np.max(H[:, 0]) - np.min(H[:, 0])
|
| 194 |
thoracic_width = max(np.max(RL[:, 0]), np.max(LL[:, 0])) - min(np.min(RL[:, 0]), np.min(LL[:, 0]))
|
| 195 |
ctr = cardiac_width / thoracic_width if thoracic_width > 0 else 0
|
| 196 |
-
return round(ctr, 3)
|
| 197 |
|
| 198 |
|
| 199 |
def segment(input_img):
|
|
@@ -216,22 +282,30 @@ def segment(input_img):
|
|
| 216 |
|
| 217 |
output = output.astype('int')
|
| 218 |
|
| 219 |
-
outseg = drawOnTop(input_img, output, original_shape)
|
| 220 |
|
| 221 |
seg_to_save = (outseg.copy() * 255).astype('uint8')
|
| 222 |
cv2.imwrite("tmp/overlap_segmentation.png", cv2.cvtColor(seg_to_save, cv2.COLOR_RGB2BGR))
|
| 223 |
|
| 224 |
-
ctr_value = calculate_ctr(output)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
if ctr_value < 0.5:
|
| 226 |
-
interpretation = "Normal"
|
| 227 |
elif 0.51 <= ctr_value <= 0.55:
|
| 228 |
-
interpretation = "Mild Cardiomegaly (CTR 51-55%)"
|
| 229 |
elif 0.56 <= ctr_value <= 0.60:
|
| 230 |
-
interpretation = "Moderate Cardiomegaly (CTR 56-60%)"
|
| 231 |
elif ctr_value > 0.60:
|
| 232 |
-
interpretation = "Severe Cardiomegaly (CTR > 60%)"
|
| 233 |
else:
|
| 234 |
-
interpretation = "Cardiomegaly"
|
| 235 |
|
| 236 |
return outseg, "tmp/overlap_segmentation.png", ctr_value, interpretation
|
| 237 |
|
|
|
|
| 49 |
return RL_mask, LL_mask, H_mask
|
| 50 |
|
| 51 |
|
| 52 |
+
def calculate_image_tilt(landmarks):
|
| 53 |
+
"""Calculate image tilt angle based on lung symmetry"""
|
| 54 |
+
RL = landmarks[0:44] # Right lung
|
| 55 |
+
LL = landmarks[44:94] # Left lung
|
| 56 |
+
|
| 57 |
+
# Find the topmost points of both lungs
|
| 58 |
+
rl_top_idx = np.argmin(RL[:, 1])
|
| 59 |
+
ll_top_idx = np.argmin(LL[:, 1])
|
| 60 |
+
|
| 61 |
+
rl_top = RL[rl_top_idx]
|
| 62 |
+
ll_top = LL[ll_top_idx]
|
| 63 |
+
|
| 64 |
+
# Calculate angle between the line connecting lung tops and horizontal
|
| 65 |
+
dx = ll_top[0] - rl_top[0]
|
| 66 |
+
dy = ll_top[1] - rl_top[1]
|
| 67 |
+
|
| 68 |
+
angle_rad = np.arctan2(dy, dx)
|
| 69 |
+
angle_deg = np.degrees(angle_rad)
|
| 70 |
+
|
| 71 |
+
return angle_deg, rl_top, ll_top
|
| 72 |
+
|
| 73 |
+
def rotate_points(points, angle_deg, center):
|
| 74 |
+
"""Rotate points around a center by given angle"""
|
| 75 |
+
angle_rad = np.radians(-angle_deg) # Negative to correct the tilt
|
| 76 |
+
cos_a = np.cos(angle_rad)
|
| 77 |
+
sin_a = np.sin(angle_rad)
|
| 78 |
+
|
| 79 |
+
# Translate to origin
|
| 80 |
+
translated = points - center
|
| 81 |
+
|
| 82 |
+
# Rotate
|
| 83 |
+
rotated = np.zeros_like(translated)
|
| 84 |
+
rotated[:, 0] = translated[:, 0] * cos_a - translated[:, 1] * sin_a
|
| 85 |
+
rotated[:, 1] = translated[:, 0] * sin_a + translated[:, 1] * cos_a
|
| 86 |
+
|
| 87 |
+
# Translate back
|
| 88 |
+
return rotated + center
|
| 89 |
+
|
| 90 |
def drawOnTop(img, landmarks, original_shape):
|
| 91 |
h, w = original_shape
|
| 92 |
output = getDenseMask(landmarks, h, w)
|
|
|
|
| 100 |
|
| 101 |
RL, LL, H = landmarks[0:44], landmarks[44:94], landmarks[94:]
|
| 102 |
|
| 103 |
+
# Calculate image tilt and correct it for measurements
|
| 104 |
+
tilt_angle, rl_top, ll_top = calculate_image_tilt(landmarks)
|
| 105 |
+
image_center = np.array([w/2, h/2])
|
| 106 |
+
|
| 107 |
+
# Draw tilt reference line (green)
|
| 108 |
+
image = cv2.line(image, (int(rl_top[0]), int(rl_top[1])), (int(ll_top[0]), int(ll_top[1])), (0, 1, 0), 1)
|
| 109 |
+
|
| 110 |
+
# Add tilt angle text
|
| 111 |
+
tilt_text = f"Tilt: {tilt_angle:.1f}°"
|
| 112 |
+
cv2.putText(image, tilt_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 1, 0), 2)
|
| 113 |
+
|
| 114 |
+
# Correct landmarks for tilt
|
| 115 |
+
if abs(tilt_angle) > 2: # Only correct if tilt is significant
|
| 116 |
+
RL_corrected = rotate_points(RL, tilt_angle, image_center)
|
| 117 |
+
LL_corrected = rotate_points(LL, tilt_angle, image_center)
|
| 118 |
+
H_corrected = rotate_points(H, tilt_angle, image_center)
|
| 119 |
+
cv2.putText(image, "Tilt Corrected", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (1, 1, 0), 2)
|
| 120 |
+
else:
|
| 121 |
+
RL_corrected, LL_corrected, H_corrected = RL, LL, H
|
| 122 |
+
|
| 123 |
# Draw the landmarks as dots
|
| 124 |
for l in RL:
|
| 125 |
image = cv2.circle(image, (int(l[0]), int(l[1])), 5, (1, 0, 1), -1)
|
|
|
|
| 128 |
for l in H:
|
| 129 |
image = cv2.circle(image, (int(l[0]), int(l[1])), 5, (1, 1, 0), -1)
|
| 130 |
|
| 131 |
+
# Draw horizontal lines for CTR calculation using corrected landmarks
|
| 132 |
+
# Heart (red line) - using corrected coordinates
|
| 133 |
+
heart_xmin = int(np.min(H_corrected[:, 0]))
|
| 134 |
+
heart_xmax = int(np.max(H_corrected[:, 0]))
|
| 135 |
+
heart_y = int(np.mean([H_corrected[np.argmin(H_corrected[:, 0]), 1], H_corrected[np.argmax(H_corrected[:, 0]), 1]]))
|
| 136 |
image = cv2.line(image, (heart_xmin, heart_y), (heart_xmax, heart_y), (1, 0, 0), 2)
|
| 137 |
|
| 138 |
# Add vertical lines at heart endpoints to verify widest points
|
|
|
|
| 140 |
image = cv2.line(image, (heart_xmin, heart_y - line_length), (heart_xmin, heart_y + line_length), (1, 0, 0), 2)
|
| 141 |
image = cv2.line(image, (heart_xmax, heart_y - line_length), (heart_xmax, heart_y + line_length), (1, 0, 0), 2)
|
| 142 |
|
| 143 |
+
# Thorax (blue line) - using corrected coordinates
|
| 144 |
+
thorax_xmin = int(min(np.min(RL_corrected[:, 0]), np.min(LL_corrected[:, 0])))
|
| 145 |
+
thorax_xmax = int(max(np.max(RL_corrected[:, 0]), np.max(LL_corrected[:, 0])))
|
| 146 |
# Find y at leftmost and rightmost points
|
| 147 |
+
if np.min(RL_corrected[:, 0]) < np.min(LL_corrected[:, 0]):
|
| 148 |
+
thorax_ymin = RL_corrected[np.argmin(RL_corrected[:, 0]), 1]
|
| 149 |
else:
|
| 150 |
+
thorax_ymin = LL_corrected[np.argmin(LL_corrected[:, 0]), 1]
|
| 151 |
+
if np.max(RL_corrected[:, 0]) > np.max(LL_corrected[:, 0]):
|
| 152 |
+
thorax_ymax = RL_corrected[np.argmax(RL_corrected[:, 0]), 1]
|
| 153 |
else:
|
| 154 |
+
thorax_ymax = LL_corrected[np.argmax(LL_corrected[:, 0]), 1]
|
| 155 |
thorax_y = int(np.mean([thorax_ymin, thorax_ymax]))
|
| 156 |
image = cv2.line(image, (thorax_xmin, thorax_y), (thorax_xmax, thorax_y), (0, 0, 1), 2)
|
| 157 |
|
| 158 |
# Add vertical lines at thorax endpoints to verify widest points
|
| 159 |
image = cv2.line(image, (thorax_xmin, thorax_y - line_length), (thorax_xmin, thorax_y + line_length), (0, 0, 1), 2)
|
| 160 |
image = cv2.line(image, (thorax_xmax, thorax_y - line_length), (thorax_xmax, thorax_y + line_length), (0, 0, 1), 2)
|
| 161 |
+
|
| 162 |
+
# Store corrected landmarks for CTR calculation
|
| 163 |
+
return image, (RL_corrected, LL_corrected, H_corrected, tilt_angle)
|
| 164 |
|
| 165 |
return image
|
| 166 |
|
|
|
|
| 247 |
return output
|
| 248 |
|
| 249 |
|
| 250 |
+
def calculate_ctr(landmarks, corrected_landmarks=None):
|
| 251 |
+
if corrected_landmarks is not None:
|
| 252 |
+
RL, LL, H, tilt_angle = corrected_landmarks
|
| 253 |
+
else:
|
| 254 |
+
H = landmarks[94:]
|
| 255 |
+
RL = landmarks[0:44]
|
| 256 |
+
LL = landmarks[44:94]
|
| 257 |
+
tilt_angle = 0
|
| 258 |
+
|
| 259 |
cardiac_width = np.max(H[:, 0]) - np.min(H[:, 0])
|
| 260 |
thoracic_width = max(np.max(RL[:, 0]), np.max(LL[:, 0])) - min(np.min(RL[:, 0]), np.min(LL[:, 0]))
|
| 261 |
ctr = cardiac_width / thoracic_width if thoracic_width > 0 else 0
|
| 262 |
+
return round(ctr, 3), abs(tilt_angle)
|
| 263 |
|
| 264 |
|
| 265 |
def segment(input_img):
|
|
|
|
| 282 |
|
| 283 |
output = output.astype('int')
|
| 284 |
|
| 285 |
+
outseg, corrected_data = drawOnTop(input_img, output, original_shape)
|
| 286 |
|
| 287 |
seg_to_save = (outseg.copy() * 255).astype('uint8')
|
| 288 |
cv2.imwrite("tmp/overlap_segmentation.png", cv2.cvtColor(seg_to_save, cv2.COLOR_RGB2BGR))
|
| 289 |
|
| 290 |
+
ctr_value, tilt_angle = calculate_ctr(output, corrected_data)
|
| 291 |
+
|
| 292 |
+
# Add tilt warning to interpretation
|
| 293 |
+
tilt_warning = ""
|
| 294 |
+
if tilt_angle > 5:
|
| 295 |
+
tilt_warning = f" (⚠️ Image tilted {tilt_angle:.1f}° - measurement corrected)"
|
| 296 |
+
elif tilt_angle > 2:
|
| 297 |
+
tilt_warning = f" (Image tilted {tilt_angle:.1f}° - corrected)"
|
| 298 |
+
|
| 299 |
if ctr_value < 0.5:
|
| 300 |
+
interpretation = f"Normal{tilt_warning}"
|
| 301 |
elif 0.51 <= ctr_value <= 0.55:
|
| 302 |
+
interpretation = f"Mild Cardiomegaly (CTR 51-55%){tilt_warning}"
|
| 303 |
elif 0.56 <= ctr_value <= 0.60:
|
| 304 |
+
interpretation = f"Moderate Cardiomegaly (CTR 56-60%){tilt_warning}"
|
| 305 |
elif ctr_value > 0.60:
|
| 306 |
+
interpretation = f"Severe Cardiomegaly (CTR > 60%){tilt_warning}"
|
| 307 |
else:
|
| 308 |
+
interpretation = f"Cardiomegaly{tilt_warning}"
|
| 309 |
|
| 310 |
return outseg, "tmp/overlap_segmentation.png", ctr_value, interpretation
|
| 311 |
|