Chayanat commited on
Commit
cd1affb
·
verified ·
1 Parent(s): dba1a4d
Files changed (1) hide show
  1. app.py +148 -32
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} degrees"
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
@@ -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])), 5, (1, 0, 1), -1)
126
  for l in LL:
127
- 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 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
- # Heart (red line) - calculate positions from corrected coordinates
135
- heart_xmin_corrected = np.min(H_corrected[:, 0])
136
- heart_xmax_corrected = np.max(H_corrected[:, 0])
137
- heart_y_corrected = np.mean([H_corrected[np.argmin(H_corrected[:, 0]), 1], H_corrected[np.argmax(H_corrected[:, 0]), 1]])
 
 
 
 
138
 
139
  # Rotate back to match the tilted image for display
140
- heart_points_corrected = np.array([[heart_xmin_corrected, heart_y_corrected], [heart_xmax_corrected, heart_y_corrected]])
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
- thorax_xmin_corrected = min(np.min(RL_corrected[:, 0]), np.min(LL_corrected[:, 0]))
170
- thorax_xmax_corrected = max(np.max(RL_corrected[:, 0]), np.max(LL_corrected[:, 0]))
171
 
172
- # Find y at leftmost and rightmost points (corrected)
173
- if np.min(RL_corrected[:, 0]) < np.min(LL_corrected[:, 0]):
174
- thorax_ymin_corrected = RL_corrected[np.argmin(RL_corrected[:, 0]), 1]
175
- else:
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([[thorax_xmin_corrected, thorax_y_corrected], [thorax_xmax_corrected, thorax_y_corrected]])
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
- cardiac_width = np.max(H[:, 0]) - np.min(H[:, 0])
306
- thoracic_width = max(np.max(RL[:, 0]), np.max(LL[:, 0])) - min(np.min(RL[:, 0]), np.min(LL[:, 0]))
307
- ctr = cardiac_width / thoracic_width if thoracic_width > 0 else 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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