Spaces:
Sleeping
Sleeping
test 006
Browse files
app.py
CHANGED
|
@@ -108,8 +108,12 @@ def drawOnTop(img, landmarks, original_shape):
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
# Correct landmarks for tilt
|
| 115 |
if abs(tilt_angle) > 2: # Only correct if tilt is significant
|
|
@@ -122,22 +126,35 @@ def drawOnTop(img, landmarks, original_shape):
|
|
| 122 |
|
| 123 |
# Draw the landmarks as dots
|
| 124 |
for l in RL:
|
| 125 |
-
image = cv2.circle(image, (int(l[0]), int(l[1])),
|
| 126 |
for l in LL:
|
| 127 |
-
image = cv2.circle(image, (int(l[0]), int(l[1])),
|
| 128 |
for l in H:
|
| 129 |
-
image = cv2.circle(image, (int(l[0]), int(l[1])),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
# Draw measurement lines that follow the image tilt for visual accuracy
|
| 132 |
# Use corrected coordinates for accurate measurement, but draw tilted lines for visual appeal
|
| 133 |
|
| 134 |
-
#
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
# Rotate back to match the tilted image for display
|
| 140 |
-
heart_points_corrected = np.array([[
|
| 141 |
heart_points_display = rotate_points(heart_points_corrected, -tilt_angle, image_center) # Rotate back for display
|
| 142 |
|
| 143 |
heart_start = (int(heart_points_display[0, 0]), int(heart_points_display[0, 1]))
|
|
@@ -165,23 +182,17 @@ def drawOnTop(img, landmarks, original_shape):
|
|
| 165 |
(int(heart_end[0] - perp_x), int(heart_end[1] - perp_y)),
|
| 166 |
(1, 0, 0), 2)
|
| 167 |
|
| 168 |
-
# Thorax (blue line) - calculate positions from corrected coordinates
|
| 169 |
-
|
| 170 |
-
|
| 171 |
|
| 172 |
-
#
|
| 173 |
-
if
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
thorax_ymin_corrected = LL_corrected[np.argmin(LL_corrected[:, 0]), 1]
|
| 177 |
-
if np.max(RL_corrected[:, 0]) > np.max(LL_corrected[:, 0]):
|
| 178 |
-
thorax_ymax_corrected = RL_corrected[np.argmax(RL_corrected[:, 0]), 1]
|
| 179 |
-
else:
|
| 180 |
-
thorax_ymax_corrected = LL_corrected[np.argmax(LL_corrected[:, 0]), 1]
|
| 181 |
-
thorax_y_corrected = np.mean([thorax_ymin_corrected, thorax_ymax_corrected])
|
| 182 |
|
| 183 |
# Rotate back to match the tilted image for display
|
| 184 |
-
thorax_points_corrected = np.array([[
|
| 185 |
thorax_points_display = rotate_points(thorax_points_corrected, -tilt_angle, image_center) # Rotate back for display
|
| 186 |
|
| 187 |
thorax_start = (int(thorax_points_display[0, 0]), int(thorax_points_display[0, 1]))
|
|
@@ -293,6 +304,80 @@ def removePreprocess(output, info):
|
|
| 293 |
return output
|
| 294 |
|
| 295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
def calculate_ctr(landmarks, corrected_landmarks=None):
|
| 297 |
if corrected_landmarks is not None:
|
| 298 |
RL, LL, H, tilt_angle = corrected_landmarks
|
|
@@ -302,9 +387,37 @@ def calculate_ctr(landmarks, corrected_landmarks=None):
|
|
| 302 |
LL = landmarks[44:94]
|
| 303 |
tilt_angle = 0
|
| 304 |
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
return round(ctr, 3), abs(tilt_angle)
|
| 309 |
|
| 310 |
|
|
@@ -424,16 +537,19 @@ def segment(input_img):
|
|
| 424 |
elif tilt_angle > 2:
|
| 425 |
tilt_warning = f" (Minor tilt: {tilt_angle:.1f}°)"
|
| 426 |
|
|
|
|
|
|
|
|
|
|
| 427 |
if ctr_value < 0.5:
|
| 428 |
-
interpretation = f"Normal{rotation_warning}{tilt_warning}"
|
| 429 |
elif 0.51 <= ctr_value <= 0.55:
|
| 430 |
-
interpretation = f"Mild Cardiomegaly (CTR 51-55%){rotation_warning}{tilt_warning}"
|
| 431 |
elif 0.56 <= ctr_value <= 0.60:
|
| 432 |
-
interpretation = f"Moderate Cardiomegaly (CTR 56-60%){rotation_warning}{tilt_warning}"
|
| 433 |
elif ctr_value > 0.60:
|
| 434 |
-
interpretation = f"Severe Cardiomegaly (CTR > 60%){rotation_warning}{tilt_warning}"
|
| 435 |
else:
|
| 436 |
-
interpretation = f"Cardiomegaly{rotation_warning}{tilt_warning}"
|
| 437 |
|
| 438 |
return outseg, "tmp/overlap_segmentation.png", ctr_value, interpretation
|
| 439 |
|
|
|
|
| 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.6, (0, 1, 0), 2)
|
| 113 |
+
|
| 114 |
+
# Add precision info
|
| 115 |
+
precision_text = f"Enhanced: {len(H_dense)} heart pts, {len(RL_dense)+len(LL_dense)} lung pts"
|
| 116 |
+
cv2.putText(image, precision_text, (10, h-10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (1, 1, 1), 1)
|
| 117 |
|
| 118 |
# Correct landmarks for tilt
|
| 119 |
if abs(tilt_angle) > 2: # Only correct if tilt is significant
|
|
|
|
| 126 |
|
| 127 |
# Draw the landmarks as dots
|
| 128 |
for l in RL:
|
| 129 |
+
image = cv2.circle(image, (int(l[0]), int(l[1])), 3, (1, 0, 1), -1)
|
| 130 |
for l in LL:
|
| 131 |
+
image = cv2.circle(image, (int(l[0]), int(l[1])), 3, (1, 0, 1), -1)
|
| 132 |
for l in H:
|
| 133 |
+
image = cv2.circle(image, (int(l[0]), int(l[1])), 3, (1, 1, 0), -1)
|
| 134 |
+
|
| 135 |
+
# Highlight the precise extreme points used for measurement
|
| 136 |
+
# Heart extreme points (larger red circles)
|
| 137 |
+
image = cv2.circle(image, (int(heart_left[0]), int(heart_left[1])), 8, (1, 0, 0), -1)
|
| 138 |
+
image = cv2.circle(image, (int(heart_right[0]), int(heart_right[1])), 8, (1, 0, 0), -1)
|
| 139 |
+
|
| 140 |
+
# Thorax extreme points (larger blue circles)
|
| 141 |
+
image = cv2.circle(image, (int(thorax_left[0]), int(thorax_left[1])), 8, (0, 0, 1), -1)
|
| 142 |
+
image = cv2.circle(image, (int(thorax_right[0]), int(thorax_right[1])), 8, (0, 0, 1), -1)
|
| 143 |
|
| 144 |
# Draw measurement lines that follow the image tilt for visual accuracy
|
| 145 |
# Use corrected coordinates for accurate measurement, but draw tilted lines for visual appeal
|
| 146 |
|
| 147 |
+
# Get interpolated points for more precise measurements
|
| 148 |
+
H_dense = interpolate_contour_points(H_corrected, num_points=300)
|
| 149 |
+
RL_dense = interpolate_contour_points(RL_corrected, num_points=200)
|
| 150 |
+
LL_dense = interpolate_contour_points(LL_corrected, num_points=200)
|
| 151 |
+
|
| 152 |
+
# Heart (red line) - calculate positions from corrected coordinates using precise method
|
| 153 |
+
heart_left, heart_right = find_precise_extremes(H_dense, direction='horizontal')
|
| 154 |
+
heart_y_corrected = np.mean([heart_left[1], heart_right[1]])
|
| 155 |
|
| 156 |
# Rotate back to match the tilted image for display
|
| 157 |
+
heart_points_corrected = np.array([[heart_left[0], heart_y_corrected], [heart_right[0], heart_y_corrected]])
|
| 158 |
heart_points_display = rotate_points(heart_points_corrected, -tilt_angle, image_center) # Rotate back for display
|
| 159 |
|
| 160 |
heart_start = (int(heart_points_display[0, 0]), int(heart_points_display[0, 1]))
|
|
|
|
| 182 |
(int(heart_end[0] - perp_x), int(heart_end[1] - perp_y)),
|
| 183 |
(1, 0, 0), 2)
|
| 184 |
|
| 185 |
+
# Thorax (blue line) - calculate positions from corrected coordinates using precise method
|
| 186 |
+
rl_left, rl_right = find_precise_extremes(RL_dense, direction='horizontal')
|
| 187 |
+
ll_left, ll_right = find_precise_extremes(LL_dense, direction='horizontal')
|
| 188 |
|
| 189 |
+
# Get the overall leftmost and rightmost points
|
| 190 |
+
thorax_left = rl_left if rl_left[0] < ll_left[0] else ll_left
|
| 191 |
+
thorax_right = rl_right if rl_right[0] > ll_right[0] else ll_right
|
| 192 |
+
thorax_y_corrected = np.mean([thorax_left[1], thorax_right[1]])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
# Rotate back to match the tilted image for display
|
| 195 |
+
thorax_points_corrected = np.array([[thorax_left[0], thorax_y_corrected], [thorax_right[0], thorax_y_corrected]])
|
| 196 |
thorax_points_display = rotate_points(thorax_points_corrected, -tilt_angle, image_center) # Rotate back for display
|
| 197 |
|
| 198 |
thorax_start = (int(thorax_points_display[0, 0]), int(thorax_points_display[0, 1]))
|
|
|
|
| 304 |
return output
|
| 305 |
|
| 306 |
|
| 307 |
+
def interpolate_contour_points(points, num_points=200):
|
| 308 |
+
"""Interpolate additional points along a contour for more precise measurements"""
|
| 309 |
+
if len(points) < 3:
|
| 310 |
+
return points
|
| 311 |
+
|
| 312 |
+
# Close the contour if not already closed
|
| 313 |
+
if not np.array_equal(points[0], points[-1]):
|
| 314 |
+
points = np.vstack([points, points[0]])
|
| 315 |
+
|
| 316 |
+
# Calculate cumulative distances along the contour
|
| 317 |
+
distances = np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1))
|
| 318 |
+
cumulative_distances = np.concatenate([[0], np.cumsum(distances)])
|
| 319 |
+
|
| 320 |
+
# Create new evenly spaced parameter values
|
| 321 |
+
total_length = cumulative_distances[-1]
|
| 322 |
+
new_params = np.linspace(0, total_length, num_points)
|
| 323 |
+
|
| 324 |
+
# Interpolate x and y coordinates
|
| 325 |
+
new_x = np.interp(new_params, cumulative_distances, points[:, 0])
|
| 326 |
+
new_y = np.interp(new_params, cumulative_distances, points[:, 1])
|
| 327 |
+
|
| 328 |
+
return np.column_stack([new_x, new_y]).astype(int)
|
| 329 |
+
|
| 330 |
+
def find_precise_extremes(points, direction='horizontal'):
|
| 331 |
+
"""Find extreme points with sub-pixel precision using parabolic fitting"""
|
| 332 |
+
if direction == 'horizontal':
|
| 333 |
+
# Find leftmost and rightmost points
|
| 334 |
+
coord_idx = 0 # x-coordinate
|
| 335 |
+
else:
|
| 336 |
+
# Find topmost and bottommost points
|
| 337 |
+
coord_idx = 1 # y-coordinate
|
| 338 |
+
|
| 339 |
+
# Find approximate extremes
|
| 340 |
+
min_idx = np.argmin(points[:, coord_idx])
|
| 341 |
+
max_idx = np.argmax(points[:, coord_idx])
|
| 342 |
+
|
| 343 |
+
def refine_extreme(idx, is_maximum=False):
|
| 344 |
+
"""Refine extreme point using neighboring points"""
|
| 345 |
+
if len(points) < 3:
|
| 346 |
+
return points[idx]
|
| 347 |
+
|
| 348 |
+
# Get neighboring indices (with wrapping)
|
| 349 |
+
prev_idx = (idx - 1) % len(points)
|
| 350 |
+
next_idx = (idx + 1) % len(points)
|
| 351 |
+
|
| 352 |
+
# Get the three points
|
| 353 |
+
p1 = points[prev_idx]
|
| 354 |
+
p2 = points[idx]
|
| 355 |
+
p3 = points[next_idx]
|
| 356 |
+
|
| 357 |
+
# If we can fit a parabola and find a better extreme, use it
|
| 358 |
+
try:
|
| 359 |
+
# Simple parabolic fitting in the coordinate direction
|
| 360 |
+
coords = np.array([p1[coord_idx], p2[coord_idx], p3[coord_idx]])
|
| 361 |
+
if is_maximum:
|
| 362 |
+
best_idx = np.argmax(coords)
|
| 363 |
+
else:
|
| 364 |
+
best_idx = np.argmin(coords)
|
| 365 |
+
|
| 366 |
+
if best_idx == 1: # Original point is still the best
|
| 367 |
+
return p2
|
| 368 |
+
elif best_idx == 0:
|
| 369 |
+
return p1
|
| 370 |
+
else:
|
| 371 |
+
return p3
|
| 372 |
+
except:
|
| 373 |
+
return p2
|
| 374 |
+
|
| 375 |
+
# Refine the extreme points
|
| 376 |
+
min_point = refine_extreme(min_idx, is_maximum=False)
|
| 377 |
+
max_point = refine_extreme(max_idx, is_maximum=True)
|
| 378 |
+
|
| 379 |
+
return min_point, max_point
|
| 380 |
+
|
| 381 |
def calculate_ctr(landmarks, corrected_landmarks=None):
|
| 382 |
if corrected_landmarks is not None:
|
| 383 |
RL, LL, H, tilt_angle = corrected_landmarks
|
|
|
|
| 387 |
LL = landmarks[44:94]
|
| 388 |
tilt_angle = 0
|
| 389 |
|
| 390 |
+
# Interpolate more points for better precision
|
| 391 |
+
RL_dense = interpolate_contour_points(RL, num_points=200)
|
| 392 |
+
LL_dense = interpolate_contour_points(LL, num_points=200)
|
| 393 |
+
H_dense = interpolate_contour_points(H, num_points=300)
|
| 394 |
+
|
| 395 |
+
# Find precise extreme points for heart
|
| 396 |
+
heart_left, heart_right = find_precise_extremes(H_dense, direction='horizontal')
|
| 397 |
+
cardiac_width = heart_right[0] - heart_left[0]
|
| 398 |
+
|
| 399 |
+
# Find precise extreme points for thorax (combining both lungs)
|
| 400 |
+
rl_left, rl_right = find_precise_extremes(RL_dense, direction='horizontal')
|
| 401 |
+
ll_left, ll_right = find_precise_extremes(LL_dense, direction='horizontal')
|
| 402 |
+
|
| 403 |
+
# Get the overall leftmost and rightmost points
|
| 404 |
+
thorax_left = rl_left if rl_left[0] < ll_left[0] else ll_left
|
| 405 |
+
thorax_right = rl_right if rl_right[0] > ll_right[0] else ll_right
|
| 406 |
+
thoracic_width = thorax_right[0] - thorax_left[0]
|
| 407 |
+
|
| 408 |
+
# Calculate CTR with additional precision checks
|
| 409 |
+
if thoracic_width > 0 and cardiac_width > 0:
|
| 410 |
+
ctr = cardiac_width / thoracic_width
|
| 411 |
+
|
| 412 |
+
# Sanity check: CTR should be between 0.2 and 1.0
|
| 413 |
+
if ctr < 0.2 or ctr > 1.0:
|
| 414 |
+
# Fallback to simple calculation
|
| 415 |
+
cardiac_width_simple = np.max(H[:, 0]) - np.min(H[:, 0])
|
| 416 |
+
thoracic_width_simple = max(np.max(RL[:, 0]), np.max(LL[:, 0])) - min(np.min(RL[:, 0]), np.min(LL[:, 0]))
|
| 417 |
+
ctr = cardiac_width_simple / thoracic_width_simple if thoracic_width_simple > 0 else 0
|
| 418 |
+
else:
|
| 419 |
+
ctr = 0
|
| 420 |
+
|
| 421 |
return round(ctr, 3), abs(tilt_angle)
|
| 422 |
|
| 423 |
|
|
|
|
| 537 |
elif tilt_angle > 2:
|
| 538 |
tilt_warning = f" (Minor tilt: {tilt_angle:.1f}°)"
|
| 539 |
|
| 540 |
+
# Enhanced interpretation with precision info
|
| 541 |
+
precision_note = " [Enhanced precision: 500+ interpolated points]"
|
| 542 |
+
|
| 543 |
if ctr_value < 0.5:
|
| 544 |
+
interpretation = f"Normal{rotation_warning}{tilt_warning}{precision_note}"
|
| 545 |
elif 0.51 <= ctr_value <= 0.55:
|
| 546 |
+
interpretation = f"Mild Cardiomegaly (CTR 51-55%){rotation_warning}{tilt_warning}{precision_note}"
|
| 547 |
elif 0.56 <= ctr_value <= 0.60:
|
| 548 |
+
interpretation = f"Moderate Cardiomegaly (CTR 56-60%){rotation_warning}{tilt_warning}{precision_note}"
|
| 549 |
elif ctr_value > 0.60:
|
| 550 |
+
interpretation = f"Severe Cardiomegaly (CTR > 60%){rotation_warning}{tilt_warning}{precision_note}"
|
| 551 |
else:
|
| 552 |
+
interpretation = f"Cardiomegaly{rotation_warning}{tilt_warning}{precision_note}"
|
| 553 |
|
| 554 |
return outseg, "tmp/overlap_segmentation.png", ctr_value, interpretation
|
| 555 |
|