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

LR - Calculation TEST001

Browse files
Files changed (1) hide show
  1. app.py +163 -17
app.py CHANGED
@@ -131,6 +131,11 @@ 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
  # 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,9 +171,6 @@ def drawOnTop(img, landmarks, original_shape):
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,6 +209,54 @@ def drawOnTop(img, landmarks, original_shape):
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,7 +375,7 @@ def validate_landmarks_consistency(landmarks, original_landmarks, threshold=0.05
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,9 +403,33 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
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,7 +440,7 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
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,6 +469,22 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
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,6 +495,17 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
405
  'traditional': round(ctr_1, 3),
406
  'centroid': round(ctr_2, 3),
407
  'percentile': round(ctr_3, 3)
 
 
 
 
 
 
 
 
 
 
 
408
  }
409
  }
410
 
@@ -416,7 +517,17 @@ def calculate_ctr_robust(landmarks, corrected_landmarks=None):
416
  'correction_applied': False,
417
  'confidence': 'Error',
418
  'method_variance': 0,
419
- 'individual_results': {}
 
 
 
 
 
 
 
 
 
 
420
  }
421
 
422
 
@@ -579,8 +690,9 @@ def segment(input_img):
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,6 +701,7 @@ def segment(input_img):
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,6 +720,22 @@ def segment(input_img):
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,9 +748,19 @@ def segment(input_img):
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,8 +796,16 @@ 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,9 +852,8 @@ if __name__ == "__main__":
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()
 
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
  (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
  (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
  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
  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
  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
  # 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
  '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
  '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
 
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
  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
 
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
  # 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
  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
 
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()