Chayanat commited on
Commit
ca4ead4
·
verified ·
1 Parent(s): 8c40f63

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +17 -163
app.py CHANGED
@@ -131,11 +131,6 @@ def drawOnTop(img, landmarks, original_shape):
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
- # Calculate thoracic midline for bilateral measurements
135
- thorax_xmin_corrected = min(np.min(RL_corrected[:, 0]), np.min(LL_corrected[:, 0]))
136
- thorax_xmax_corrected = max(np.max(RL_corrected[:, 0]), np.max(LL_corrected[:, 0]))
137
- thorax_midline_corrected = (thorax_xmin_corrected + thorax_xmax_corrected) / 2
138
-
139
  # Heart (red line) - calculate positions from corrected coordinates
140
  heart_xmin_corrected = np.min(H_corrected[:, 0])
141
  heart_xmax_corrected = np.max(H_corrected[:, 0])
@@ -171,6 +166,9 @@ def drawOnTop(img, landmarks, original_shape):
171
  (1, 0, 0), 2)
172
 
173
  # Thorax (blue line) - calculate positions from corrected coordinates
 
 
 
174
  # Find y at leftmost and rightmost points (corrected)
175
  if np.min(RL_corrected[:, 0]) < np.min(LL_corrected[:, 0]):
176
  thorax_ymin_corrected = RL_corrected[np.argmin(RL_corrected[:, 0]), 1]
@@ -209,54 +207,6 @@ def drawOnTop(img, landmarks, original_shape):
209
  (int(thorax_end[0] - perp_x), int(thorax_end[1] - perp_y)),
210
  (0, 0, 1), 2)
211
 
212
- # === DRAW BILATERAL MEASUREMENT LINES ===
213
- # Draw thoracic midline (vertical white dashed line)
214
- midline_points_corrected = np.array([[thorax_midline_corrected, thorax_y_corrected - 100],
215
- [thorax_midline_corrected, thorax_y_corrected + 100]])
216
- midline_points_display = rotate_points(midline_points_corrected, -tilt_angle, image_center)
217
-
218
- midline_start = (int(midline_points_display[0, 0]), int(midline_points_display[0, 1]))
219
- midline_end = (int(midline_points_display[1, 0]), int(midline_points_display[1, 1]))
220
-
221
- # Draw dashed line by drawing short segments
222
- dash_length = 10
223
- gap_length = 5
224
- total_length = np.sqrt((midline_end[0] - midline_start[0])**2 + (midline_end[1] - midline_start[1])**2)
225
- num_segments = int(total_length / (dash_length + gap_length))
226
-
227
- for i in range(0, num_segments, 2): # Every other segment for dashed effect
228
- start_ratio = i * (dash_length + gap_length) / total_length
229
- end_ratio = (i * (dash_length + gap_length) + dash_length) / total_length
230
- if end_ratio > 1:
231
- end_ratio = 1
232
-
233
- dash_start = (
234
- int(midline_start[0] + start_ratio * (midline_end[0] - midline_start[0])),
235
- int(midline_start[1] + start_ratio * (midline_end[1] - midline_start[1]))
236
- )
237
- dash_end = (
238
- int(midline_start[0] + end_ratio * (midline_end[0] - midline_start[0])),
239
- int(midline_start[1] + end_ratio * (midline_end[1] - midline_start[1]))
240
- )
241
- image = cv2.line(image, dash_start, dash_end, (1, 1, 1), 2)
242
-
243
- # Draw bilateral measurement lines
244
- # Left side cardiac measurement (cyan)
245
- left_cardiac_points = np.array([[thorax_midline_corrected, heart_y_corrected], [heart_xmin_corrected, heart_y_corrected]])
246
- left_cardiac_display = rotate_points(left_cardiac_points, -tilt_angle, image_center)
247
- image = cv2.line(image,
248
- (int(left_cardiac_display[0, 0]), int(left_cardiac_display[0, 1])),
249
- (int(left_cardiac_display[1, 0]), int(left_cardiac_display[1, 1])),
250
- (0, 1, 1), 2)
251
-
252
- # Right side cardiac measurement (yellow)
253
- right_cardiac_points = np.array([[thorax_midline_corrected, heart_y_corrected], [heart_xmax_corrected, heart_y_corrected]])
254
- right_cardiac_display = rotate_points(right_cardiac_points, -tilt_angle, image_center)
255
- image = cv2.line(image,
256
- (int(right_cardiac_display[0, 0]), int(right_cardiac_display[0, 1])),
257
- (int(right_cardiac_display[1, 0]), int(right_cardiac_display[1, 1])),
258
- (1, 1, 0), 2)
259
-
260
  # Store corrected landmarks for CTR calculation
261
  return image, (RL_corrected, LL_corrected, H_corrected, tilt_angle)
262
 
@@ -375,7 +325,7 @@ def validate_landmarks_consistency(landmarks, original_landmarks, threshold=0.05
375
  return False
376
 
377
  def calculate_ctr_robust(landmarks, corrected_landmarks=None):
378
- """Calculate CTR with multiple validation steps and bilateral analysis"""
379
  try:
380
  original_landmarks = landmarks.copy()
381
 
@@ -403,33 +353,9 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
403
  tilt_angle = 0
404
  correction_applied = False
405
 
406
- # Calculate thoracic midline for bilateral analysis
407
- thoracic_left_edge = min(np.min(RL[:, 0]), np.min(LL[:, 0]))
408
- thoracic_right_edge = max(np.max(RL[:, 0]), np.max(LL[:, 0]))
409
- thoracic_midline = (thoracic_left_edge + thoracic_right_edge) / 2
410
- thoracic_width = thoracic_right_edge - thoracic_left_edge
411
-
412
- # Calculate cardiac boundaries
413
- cardiac_left_edge = np.min(H[:, 0])
414
- cardiac_right_edge = np.max(H[:, 0])
415
- cardiac_center = np.mean(H[:, 0])
416
- cardiac_width = cardiac_right_edge - cardiac_left_edge
417
-
418
- # === BILATERAL CTR ANALYSIS ===
419
- # Left-side measurements (from thoracic midline to left edge)
420
- left_thoracic_width = thoracic_midline - thoracic_left_edge
421
- left_cardiac_width = thoracic_midline - cardiac_left_edge
422
- left_ctr = left_cardiac_width / left_thoracic_width if left_thoracic_width > 0 else 0
423
-
424
- # Right-side measurements (from thoracic midline to right edge)
425
- right_thoracic_width = thoracic_right_edge - thoracic_midline
426
- right_cardiac_width = cardiac_right_edge - thoracic_midline
427
- right_ctr = right_cardiac_width / right_thoracic_width if right_thoracic_width > 0 else 0
428
-
429
- # === TRADITIONAL TOTAL CTR METHODS ===
430
  # Method 1: Traditional width measurement
431
- cardiac_width_1 = cardiac_width
432
- thoracic_width_1 = thoracic_width
433
 
434
  # Method 2: Centroid-based measurement (more robust to outliers)
435
  h_centroid = np.mean(H, axis=0)
@@ -440,7 +366,7 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
440
  h_distances = np.linalg.norm(H - h_centroid, axis=1)
441
  cardiac_width_2 = 2 * np.max(h_distances)
442
 
443
- thoracic_width_2 = thoracic_width
444
 
445
  # Method 3: Percentile-based measurement (removes extreme outliers)
446
  cardiac_x_coords = H[:, 0]
@@ -469,22 +395,6 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
469
  # Use median of methods for final result
470
  final_ctr = np.median(ctr_values)
471
 
472
- # === ASYMMETRY ANALYSIS ===
473
- # Calculate cardiac asymmetry ratio (how much heart deviates to one side)
474
- cardiac_shift = cardiac_center - thoracic_midline
475
- cardiac_asymmetry_ratio = abs(cardiac_shift) / (thoracic_width / 2) if thoracic_width > 0 else 0
476
-
477
- # Determine which side the heart is shifted towards
478
- if cardiac_shift > thoracic_width * 0.02: # > 2% of thoracic width
479
- heart_position = "Right-shifted"
480
- elif cardiac_shift < -thoracic_width * 0.02: # < -2% of thoracic width
481
- heart_position = "Left-shifted"
482
- else:
483
- heart_position = "Centered"
484
-
485
- # Calculate bilateral ratio (left CTR / right CTR)
486
- bilateral_ratio = left_ctr / right_ctr if right_ctr > 0 else 0
487
-
488
  return {
489
  'ctr': round(final_ctr, 3),
490
  'tilt_angle': abs(tilt_angle),
@@ -495,17 +405,6 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
495
  'traditional': round(ctr_1, 3),
496
  'centroid': round(ctr_2, 3),
497
  'percentile': round(ctr_3, 3)
498
- },
499
- # New bilateral analysis results
500
- 'bilateral_analysis': {
501
- 'left_ctr': round(left_ctr, 3),
502
- 'right_ctr': round(right_ctr, 3),
503
- 'bilateral_ratio': round(bilateral_ratio, 3),
504
- 'cardiac_asymmetry_ratio': round(cardiac_asymmetry_ratio, 3),
505
- 'heart_position': heart_position,
506
- 'thoracic_midline_x': round(thoracic_midline, 1),
507
- 'cardiac_center_x': round(cardiac_center, 1),
508
- 'cardiac_shift_px': round(cardiac_shift, 1)
509
  }
510
  }
511
 
@@ -517,17 +416,7 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
517
  'correction_applied': False,
518
  'confidence': 'Error',
519
  'method_variance': 0,
520
- 'individual_results': {},
521
- 'bilateral_analysis': {
522
- 'left_ctr': 0,
523
- 'right_ctr': 0,
524
- 'bilateral_ratio': 0,
525
- 'cardiac_asymmetry_ratio': 0,
526
- 'heart_position': 'Unknown',
527
- 'thoracic_midline_x': 0,
528
- 'cardiac_center_x': 0,
529
- 'cardiac_shift_px': 0
530
- }
531
  }
532
 
533
 
@@ -690,9 +579,8 @@ def segment(input_img):
690
 
691
  except Exception as e:
692
  print(f"Error in segmentation: {e}")
693
- # Return a basic error response with all expected outputs
694
- error_msg = f"Error: {str(e)}"
695
- return None, None, 0, 0, 0, 0, 0, "Error", error_msg
696
 
697
  seg_to_save = (outseg.copy() * 255).astype('uint8')
698
  cv2.imwrite("tmp/overlap_segmentation.png", cv2.cvtColor(seg_to_save, cv2.COLOR_RGB2BGR))
@@ -701,7 +589,6 @@ def segment(input_img):
701
  ctr_result = calculate_ctr_robust(output, corrected_data)
702
  ctr_value = ctr_result['ctr']
703
  tilt_angle = ctr_result['tilt_angle']
704
- bilateral = ctr_result['bilateral_analysis']
705
 
706
  # Enhanced interpretation with quality indicators
707
  interpretation_parts = []
@@ -720,22 +607,6 @@ def segment(input_img):
720
 
721
  interpretation_parts.append(base_interpretation)
722
 
723
- # Add bilateral analysis
724
- bilateral_parts = []
725
- bilateral_parts.append(f"L-CTR: {bilateral['left_ctr']}")
726
- bilateral_parts.append(f"R-CTR: {bilateral['right_ctr']}")
727
- bilateral_parts.append(f"L/R Ratio: {bilateral['bilateral_ratio']}")
728
-
729
- # Analyze cardiac position
730
- if bilateral['heart_position'] != 'Centered':
731
- bilateral_parts.append(f"Heart: {bilateral['heart_position']}")
732
-
733
- # Check for significant asymmetry
734
- if bilateral['cardiac_asymmetry_ratio'] > 0.1:
735
- bilateral_parts.append(f"Asymmetry: {bilateral['cardiac_asymmetry_ratio']:.2f}")
736
-
737
- interpretation_parts.append(" | ".join(bilateral_parts))
738
-
739
  # Add quality indicators
740
  if was_rotated:
741
  interpretation_parts.append(f"Image rotation corrected ({detected_rotation:.1f}°)")
@@ -748,19 +619,9 @@ def segment(input_img):
748
  # Add confidence indicator
749
  interpretation_parts.append(f"Confidence: {ctr_result['confidence']}")
750
 
751
- final_interpretation = "\n".join(interpretation_parts)
752
 
753
- bilateral = ctr_result['bilateral_analysis']
754
-
755
- return (outseg,
756
- "tmp/overlap_segmentation.png",
757
- ctr_result['ctr'], # Total CTR
758
- bilateral['left_ctr'], # Left CTR
759
- bilateral['right_ctr'], # Right CTR
760
- bilateral['bilateral_ratio'], # L/R Ratio
761
- bilateral['cardiac_asymmetry_ratio'], # Asymmetry
762
- bilateral['heart_position'], # Heart Position
763
- final_interpretation) # Clinical Interpretation
764
 
765
 
766
  if __name__ == "__main__":
@@ -796,16 +657,8 @@ if __name__ == "__main__":
796
  image_output = gr.Image(type="filepath", height=750)
797
 
798
  with gr.Row():
799
- with gr.Column():
800
- ctr_total = gr.Number(label="Total CTR", precision=3)
801
- ctr_left = gr.Number(label="Left CTR", precision=3)
802
- ctr_right = gr.Number(label="Right CTR", precision=3)
803
- with gr.Column():
804
- bilateral_ratio = gr.Number(label="L/R Ratio", precision=3)
805
- asymmetry_ratio = gr.Number(label="Asymmetry", precision=3)
806
- heart_position = gr.Textbox(label="Heart Position", interactive=False)
807
-
808
- ctr_interpretation = gr.Textbox(label="Clinical Interpretation", interactive=False, lines=4)
809
 
810
  results = gr.File()
811
 
@@ -852,8 +705,9 @@ if __name__ == "__main__":
852
 
853
  clear_button.click(lambda: None, None, image_input, queue=False)
854
  clear_button.click(lambda: None, None, image_output, queue=False)
855
- clear_button.click(lambda: (None, None, None, None, None, None, None), None, [ctr_total, ctr_left, ctr_right, bilateral_ratio, asymmetry_ratio, heart_position, ctr_interpretation], queue=False)
 
856
 
857
- image_button.click(segment, inputs=image_input, outputs=[image_output, results, ctr_total, ctr_left, ctr_right, bilateral_ratio, asymmetry_ratio, heart_position, ctr_interpretation], queue=False)
858
 
859
  demo.launch()
 
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])
 
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]
 
207
  (int(thorax_end[0] - perp_x), int(thorax_end[1] - perp_y)),
208
  (0, 0, 1), 2)
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  # Store corrected landmarks for CTR calculation
211
  return image, (RL_corrected, LL_corrected, H_corrected, tilt_angle)
212
 
 
325
  return False
326
 
327
  def calculate_ctr_robust(landmarks, corrected_landmarks=None):
328
+ """Calculate CTR with multiple validation steps"""
329
  try:
330
  original_landmarks = landmarks.copy()
331
 
 
353
  tilt_angle = 0
354
  correction_applied = False
355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  # Method 1: Traditional width measurement
357
+ cardiac_width_1 = np.max(H[:, 0]) - np.min(H[:, 0])
358
+ thoracic_width_1 = max(np.max(RL[:, 0]), np.max(LL[:, 0])) - min(np.min(RL[:, 0]), np.min(LL[:, 0]))
359
 
360
  # Method 2: Centroid-based measurement (more robust to outliers)
361
  h_centroid = np.mean(H, axis=0)
 
366
  h_distances = np.linalg.norm(H - h_centroid, axis=1)
367
  cardiac_width_2 = 2 * np.max(h_distances)
368
 
369
+ thoracic_width_2 = max(np.max(RL[:, 0]), np.max(LL[:, 0])) - min(np.min(RL[:, 0]), np.min(LL[:, 0]))
370
 
371
  # Method 3: Percentile-based measurement (removes extreme outliers)
372
  cardiac_x_coords = H[:, 0]
 
395
  # Use median of methods for final result
396
  final_ctr = np.median(ctr_values)
397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  return {
399
  'ctr': round(final_ctr, 3),
400
  'tilt_angle': abs(tilt_angle),
 
405
  'traditional': round(ctr_1, 3),
406
  'centroid': round(ctr_2, 3),
407
  'percentile': round(ctr_3, 3)
 
 
 
 
 
 
 
 
 
 
 
408
  }
409
  }
410
 
 
416
  'correction_applied': False,
417
  'confidence': 'Error',
418
  'method_variance': 0,
419
+ 'individual_results': {}
 
 
 
 
 
 
 
 
 
 
420
  }
421
 
422
 
 
579
 
580
  except Exception as e:
581
  print(f"Error in segmentation: {e}")
582
+ # Return a basic error response
583
+ return None, None, 0, f"Error: {str(e)}"
 
584
 
585
  seg_to_save = (outseg.copy() * 255).astype('uint8')
586
  cv2.imwrite("tmp/overlap_segmentation.png", cv2.cvtColor(seg_to_save, cv2.COLOR_RGB2BGR))
 
589
  ctr_result = calculate_ctr_robust(output, corrected_data)
590
  ctr_value = ctr_result['ctr']
591
  tilt_angle = ctr_result['tilt_angle']
 
592
 
593
  # Enhanced interpretation with quality indicators
594
  interpretation_parts = []
 
607
 
608
  interpretation_parts.append(base_interpretation)
609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
  # Add quality indicators
611
  if was_rotated:
612
  interpretation_parts.append(f"Image rotation corrected ({detected_rotation:.1f}°)")
 
619
  # Add confidence indicator
620
  interpretation_parts.append(f"Confidence: {ctr_result['confidence']}")
621
 
622
+ final_interpretation = " | ".join(interpretation_parts)
623
 
624
+ return outseg, "tmp/overlap_segmentation.png", ctr_value, final_interpretation
 
 
 
 
 
 
 
 
 
 
625
 
626
 
627
  if __name__ == "__main__":
 
657
  image_output = gr.Image(type="filepath", height=750)
658
 
659
  with gr.Row():
660
+ ctr_output = gr.Number(label="CTR (Cardiothoracic Ratio)")
661
+ ctr_interpretation = gr.Textbox(label="Interpretation", interactive=False)
 
 
 
 
 
 
 
 
662
 
663
  results = gr.File()
664
 
 
705
 
706
  clear_button.click(lambda: None, None, image_input, queue=False)
707
  clear_button.click(lambda: None, None, image_output, queue=False)
708
+ clear_button.click(lambda: None, None, ctr_output, queue=False)
709
+ clear_button.click(lambda: None, None, ctr_interpretation, queue=False)
710
 
711
+ image_button.click(segment, inputs=image_input, outputs=[image_output, results, ctr_output, ctr_interpretation], queue=False)
712
 
713
  demo.launch()