HeshamAI commited on
Commit
d0636ec
·
verified ·
1 Parent(s): a5178c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -58
app.py CHANGED
@@ -61,16 +61,11 @@ class DicomAnalyzer:
61
  self.pan_y = 0
62
  self.max_pan_x = 0
63
  self.max_pan_y = 0
64
- # Main circle color remains yellow:
65
  self.CIRCLE_COLOR = (0, 255, 255) # BGR format
66
- # Small circles inside the main circle will be white:
67
  self.SMALL_CIRCLES_COLOR = (255, 255, 255) # BGR white
68
  print("DicomAnalyzer initialized...")
69
 
70
  def save_results(self):
71
- """
72
- Basic save function for raw results with improved error handling and logging
73
- """
74
  try:
75
  if not self.results:
76
  logger.warning("Attempted to save with no results")
@@ -138,7 +133,6 @@ class DicomAnalyzer:
138
  except Exception as e:
139
  print(f"Error loading DICOM file: {str(e)}")
140
  return None, f"Error loading DICOM file: {str(e)}"
141
-
142
  def normalize_image(self, image):
143
  try:
144
  normalized = cv2.normalize(
@@ -175,12 +169,8 @@ class DicomAnalyzer:
175
  return self.update_display()
176
 
177
  def handle_keyboard(self, key):
178
- """
179
- Handle arrow keys. Pan movement is increased to be more noticeable.
180
- """
181
  try:
182
  print(f"Handling key press: {key}")
183
- # Increase pan step for bigger movement:
184
  pan_amount = int(10 * self.zoom_factor)
185
 
186
  if key == 'ArrowLeft':
@@ -198,10 +188,6 @@ class DicomAnalyzer:
198
  return self.display_image
199
 
200
  def update_display(self):
201
- """
202
- Updates the displayed image according to the current zoom/pan and draws the circles.
203
- The big circle is in CIRCLE_COLOR, and the small circles inside are in SMALL_CIRCLES_COLOR.
204
- """
205
  try:
206
  if self.original_display is None:
207
  return None
@@ -266,9 +252,6 @@ class DicomAnalyzer:
266
  return self.original_display
267
 
268
  def analyze_roi(self, evt: gr.SelectData):
269
- """
270
- Called when user clicks on the displayed image to measure an ROI.
271
- """
272
  try:
273
  if self.current_image is None:
274
  return None, "No image loaded"
@@ -276,11 +259,9 @@ class DicomAnalyzer:
276
  clicked_x = evt.index[0]
277
  clicked_y = evt.index[1]
278
 
279
- # Adjust by pan
280
  x = clicked_x + self.pan_x
281
  y = clicked_y + self.pan_y
282
 
283
- # Adjust by zoom
284
  if self.zoom_factor != 1.0:
285
  x = x / self.zoom_factor
286
  y = y / self.zoom_factor
@@ -338,7 +319,6 @@ class DicomAnalyzer:
338
  except Exception as e:
339
  print(f"Error analyzing ROI: {str(e)}")
340
  return self.display_image, f"Error analyzing ROI: {str(e)}"
341
-
342
  def add_formulas_to_template(self, ws, row_pair, col_group, red_font):
343
  """
344
  Inserts SNR (first row) and CNR (second row) formulas with IFERROR.
@@ -369,10 +349,6 @@ class DicomAnalyzer:
369
  logger.error(f"Error adding formulas: {str(e)}")
370
 
371
  def save_formatted_results(self, output_path):
372
- """
373
- Creates an Excel file with results in a formatted table,
374
- including SNR/CNR formulas and average calculations.
375
- """
376
  try:
377
  if not self.results:
378
  return None, "No results to save"
@@ -382,7 +358,6 @@ class DicomAnalyzer:
382
  red_font = openpyxl.styles.Font(color="FF0000")
383
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
384
 
385
- # Column group headers
386
  headers = ['Area', 'Mean', 'StdDev', 'Min', 'Max']
387
 
388
  column_groups = [
@@ -411,13 +386,11 @@ class DicomAnalyzer:
411
  '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)'
412
  ]
413
 
414
- # Set the phantom size row labels
415
  for i, size in enumerate(phantom_sizes):
416
  header_cell = ws.cell(row=row_pairs[i][0]-1, column=1, value=size)
417
  header_cell.font = red_font
418
  header_cell.alignment = center_alignment
419
 
420
- # Fill the data from self.results
421
  result_idx = 0
422
  current_col_group = 0
423
  current_row_pair = 0
@@ -429,19 +402,16 @@ class DicomAnalyzer:
429
  cols = column_groups[current_col_group]
430
  rows = row_pairs[current_row_pair]
431
 
432
- # First row
433
  if result_idx < len(self.results):
434
  result = self.results[result_idx]
435
  self._write_result_to_cells(ws, result, cols, rows[0])
436
  result_idx += 1
437
 
438
- # Second row
439
  if result_idx < len(self.results):
440
  result = self.results[result_idx]
441
  self._write_result_to_cells(ws, result, cols, rows[1])
442
  result_idx += 1
443
 
444
- # Add SNR/CNR formulas
445
  self.add_formulas_to_template(ws, rows, cols, red_font)
446
 
447
  current_col_group += 1
@@ -449,18 +419,14 @@ class DicomAnalyzer:
449
  current_col_group = 0
450
  current_row_pair += 1
451
 
452
- # Center align all data cells
453
  for cols in column_groups:
454
  for col in cols:
455
  for row in range(2, 31):
456
  cell = ws[f"{col}{row}"]
457
  if cell.value is not None:
458
  cell.alignment = center_alignment
459
-
460
- # Additional tables: StdDev Averages and CNR Averages
461
  current_row = 32
462
-
463
- # StdDev
464
  stddev_header = ws.cell(row=current_row, column=1, value="StdDev Averages")
465
  stddev_header.font = red_font
466
  stddev_header.alignment = center_alignment
@@ -471,7 +437,7 @@ class DicomAnalyzer:
471
  stddev_values = []
472
 
473
  for cols in column_groups:
474
- stddev_col = cols[2] # The StdDev column
475
  cell_value = ws[f"{stddev_col}{row_number}"].value
476
  if cell_value not in [0, None, '']:
477
  stddev_values.append(float(cell_value))
@@ -486,9 +452,36 @@ class DicomAnalyzer:
486
  avg_cell.alignment = center_alignment
487
  current_row += 1
488
 
 
489
  current_row += 2
 
 
 
 
490
 
491
- # CNR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  cnr_header = ws.cell(row=current_row, column=1, value="CNR Averages")
493
  cnr_header.font = red_font
494
  cnr_header.alignment = center_alignment
@@ -502,7 +495,6 @@ class DicomAnalyzer:
502
  formula_col = get_column_letter(column_index_from_string(cols[-1]) + 1)
503
  cnr_cell_ref = f"{formula_col}{row_number}"
504
 
505
- # Read Mean1, Mean2, Std2 to skip zeros
506
  mean_col = cols[1]
507
  std_col = cols[2]
508
 
@@ -517,7 +509,6 @@ class DicomAnalyzer:
517
  except:
518
  mean1_val, mean2_val, std2_val = 0, 0, 0
519
 
520
- # If not all zero, add the cell reference
521
  if not (mean1_val == 0 and mean2_val == 0 and std2_val == 0):
522
  cnr_cells.append(cnr_cell_ref)
523
 
@@ -525,9 +516,7 @@ class DicomAnalyzer:
525
  size_cell.alignment = center_alignment
526
 
527
  if cnr_cells:
528
- # Using AVERAGE(...) instead of AVERAGEIF
529
  average_formula = f'=IFERROR(AVERAGE({",".join(cnr_cells)}), "")'
530
-
531
  avg_cell = ws.cell(row=current_row, column=2)
532
  avg_cell.value = average_formula
533
  avg_cell.number_format = '0.000'
@@ -535,7 +524,6 @@ class DicomAnalyzer:
535
 
536
  current_row += 1
537
 
538
- # Align the extra rows
539
  for row in range(32, current_row):
540
  for col in range(1, 3):
541
  cell = ws.cell(row=row, column=col)
@@ -549,7 +537,6 @@ class DicomAnalyzer:
549
  return None, f"Error saving results: {str(e)}"
550
 
551
  def _write_result_to_cells(self, ws, result, cols, row):
552
- """Helper method to write a single result to worksheet cells."""
553
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
554
 
555
  value_mapping = {
@@ -567,7 +554,6 @@ class DicomAnalyzer:
567
  cell.alignment = center_alignment
568
 
569
  def format_results(self):
570
- """Returns a string representation of self.results for display."""
571
  if not self.results:
572
  return "No measurements yet"
573
  df = pd.DataFrame(self.results)
@@ -576,9 +562,6 @@ class DicomAnalyzer:
576
  return df.to_string(index=False)
577
 
578
  def add_zero_row(self, image):
579
- """
580
- Adds one row with zero-values.
581
- """
582
  self.results.append({
583
  'Area (mm²)': '0.000',
584
  'Mean': '0.000',
@@ -590,9 +573,6 @@ class DicomAnalyzer:
590
  return image, self.format_results()
591
 
592
  def add_two_zero_rows(self, image):
593
- """
594
- Adds two consecutive rows with zero-values.
595
- """
596
  for _ in range(2):
597
  self.results.append({
598
  'Area (mm²)': '0.000',
@@ -605,10 +585,20 @@ class DicomAnalyzer:
605
  return image, self.format_results()
606
 
607
  def undo_last(self, image):
608
- if self.results:
609
- self.results.pop()
610
- if self.marks:
 
 
 
 
 
 
 
 
 
611
  self.marks.pop()
 
612
  return self.update_display(), self.format_results()
613
 
614
 
@@ -643,7 +633,6 @@ def create_interface():
643
  elem_id="image_display"
644
  )
645
 
646
- # Removed the "Add Blank Row" button
647
  with gr.Row():
648
  zero_btn = gr.Button("Add Zero Row")
649
  zero2_btn = gr.Button("Add Two Zero Rows")
@@ -674,7 +663,6 @@ def create_interface():
674
  output_path = "analysis_results_formatted.xlsx"
675
  return analyzer.save_formatted_results(output_path)
676
 
677
- # Handlers
678
  file_input.change(
679
  fn=analyzer.load_dicom,
680
  inputs=file_input,
@@ -723,7 +711,6 @@ def create_interface():
723
  outputs=image_display
724
  )
725
 
726
- # Removed blank_btn
727
  zero_btn.click(
728
  fn=analyzer.add_zero_row,
729
  inputs=image_display,
@@ -752,7 +739,6 @@ def create_interface():
752
  outputs=[file_output, results_display]
753
  )
754
 
755
- # JavaScript snippet to allow multiple arrow key presses
756
  js = """
757
  <script>
758
  document.addEventListener('keydown', function(e) {
@@ -795,4 +781,4 @@ if __name__ == "__main__":
795
  print(f"Error launching application: {str(e)}")
796
  logger.error(f"Error launching application: {str(e)}")
797
  logger.error(traceback.format_exc())
798
- raise e
 
61
  self.pan_y = 0
62
  self.max_pan_x = 0
63
  self.max_pan_y = 0
 
64
  self.CIRCLE_COLOR = (0, 255, 255) # BGR format
 
65
  self.SMALL_CIRCLES_COLOR = (255, 255, 255) # BGR white
66
  print("DicomAnalyzer initialized...")
67
 
68
  def save_results(self):
 
 
 
69
  try:
70
  if not self.results:
71
  logger.warning("Attempted to save with no results")
 
133
  except Exception as e:
134
  print(f"Error loading DICOM file: {str(e)}")
135
  return None, f"Error loading DICOM file: {str(e)}"
 
136
  def normalize_image(self, image):
137
  try:
138
  normalized = cv2.normalize(
 
169
  return self.update_display()
170
 
171
  def handle_keyboard(self, key):
 
 
 
172
  try:
173
  print(f"Handling key press: {key}")
 
174
  pan_amount = int(10 * self.zoom_factor)
175
 
176
  if key == 'ArrowLeft':
 
188
  return self.display_image
189
 
190
  def update_display(self):
 
 
 
 
191
  try:
192
  if self.original_display is None:
193
  return None
 
252
  return self.original_display
253
 
254
  def analyze_roi(self, evt: gr.SelectData):
 
 
 
255
  try:
256
  if self.current_image is None:
257
  return None, "No image loaded"
 
259
  clicked_x = evt.index[0]
260
  clicked_y = evt.index[1]
261
 
 
262
  x = clicked_x + self.pan_x
263
  y = clicked_y + self.pan_y
264
 
 
265
  if self.zoom_factor != 1.0:
266
  x = x / self.zoom_factor
267
  y = y / self.zoom_factor
 
319
  except Exception as e:
320
  print(f"Error analyzing ROI: {str(e)}")
321
  return self.display_image, f"Error analyzing ROI: {str(e)}"
 
322
  def add_formulas_to_template(self, ws, row_pair, col_group, red_font):
323
  """
324
  Inserts SNR (first row) and CNR (second row) formulas with IFERROR.
 
349
  logger.error(f"Error adding formulas: {str(e)}")
350
 
351
  def save_formatted_results(self, output_path):
 
 
 
 
352
  try:
353
  if not self.results:
354
  return None, "No results to save"
 
358
  red_font = openpyxl.styles.Font(color="FF0000")
359
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
360
 
 
361
  headers = ['Area', 'Mean', 'StdDev', 'Min', 'Max']
362
 
363
  column_groups = [
 
386
  '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)'
387
  ]
388
 
 
389
  for i, size in enumerate(phantom_sizes):
390
  header_cell = ws.cell(row=row_pairs[i][0]-1, column=1, value=size)
391
  header_cell.font = red_font
392
  header_cell.alignment = center_alignment
393
 
 
394
  result_idx = 0
395
  current_col_group = 0
396
  current_row_pair = 0
 
402
  cols = column_groups[current_col_group]
403
  rows = row_pairs[current_row_pair]
404
 
 
405
  if result_idx < len(self.results):
406
  result = self.results[result_idx]
407
  self._write_result_to_cells(ws, result, cols, rows[0])
408
  result_idx += 1
409
 
 
410
  if result_idx < len(self.results):
411
  result = self.results[result_idx]
412
  self._write_result_to_cells(ws, result, cols, rows[1])
413
  result_idx += 1
414
 
 
415
  self.add_formulas_to_template(ws, rows, cols, red_font)
416
 
417
  current_col_group += 1
 
419
  current_col_group = 0
420
  current_row_pair += 1
421
 
 
422
  for cols in column_groups:
423
  for col in cols:
424
  for row in range(2, 31):
425
  cell = ws[f"{col}{row}"]
426
  if cell.value is not None:
427
  cell.alignment = center_alignment
428
+ # StdDev Averages
 
429
  current_row = 32
 
 
430
  stddev_header = ws.cell(row=current_row, column=1, value="StdDev Averages")
431
  stddev_header.font = red_font
432
  stddev_header.alignment = center_alignment
 
437
  stddev_values = []
438
 
439
  for cols in column_groups:
440
+ stddev_col = cols[2]
441
  cell_value = ws[f"{stddev_col}{row_number}"].value
442
  if cell_value not in [0, None, '']:
443
  stddev_values.append(float(cell_value))
 
452
  avg_cell.alignment = center_alignment
453
  current_row += 1
454
 
455
+ # Mean Averages
456
  current_row += 2
457
+ mean_header = ws.cell(row=current_row, column=1, value="Mean Averages")
458
+ mean_header.font = red_font
459
+ mean_header.alignment = center_alignment
460
+ current_row += 1
461
 
462
+ for i, size in enumerate(phantom_sizes):
463
+ row_number = row_pairs[i][0] # نأخذ الصف الأول فقط
464
+ mean_values = []
465
+
466
+ for cols in column_groups:
467
+ mean_col = cols[1] # The Mean column
468
+ cell_value = ws[f"{mean_col}{row_number}"].value
469
+ if cell_value not in [0, None, '']:
470
+ mean_values.append(float(cell_value))
471
+
472
+ size_cell = ws.cell(row=current_row, column=1, value=size)
473
+ size_cell.alignment = center_alignment
474
+
475
+ if mean_values:
476
+ avg_mean = sum(mean_values) / len(mean_values)
477
+ avg_cell = ws.cell(row=current_row, column=2, value=avg_mean)
478
+ avg_cell.number_format = '0.000'
479
+ avg_cell.alignment = center_alignment
480
+ current_row += 1
481
+
482
+ current_row += 2
483
+
484
+ # CNR Averages
485
  cnr_header = ws.cell(row=current_row, column=1, value="CNR Averages")
486
  cnr_header.font = red_font
487
  cnr_header.alignment = center_alignment
 
495
  formula_col = get_column_letter(column_index_from_string(cols[-1]) + 1)
496
  cnr_cell_ref = f"{formula_col}{row_number}"
497
 
 
498
  mean_col = cols[1]
499
  std_col = cols[2]
500
 
 
509
  except:
510
  mean1_val, mean2_val, std2_val = 0, 0, 0
511
 
 
512
  if not (mean1_val == 0 and mean2_val == 0 and std2_val == 0):
513
  cnr_cells.append(cnr_cell_ref)
514
 
 
516
  size_cell.alignment = center_alignment
517
 
518
  if cnr_cells:
 
519
  average_formula = f'=IFERROR(AVERAGE({",".join(cnr_cells)}), "")'
 
520
  avg_cell = ws.cell(row=current_row, column=2)
521
  avg_cell.value = average_formula
522
  avg_cell.number_format = '0.000'
 
524
 
525
  current_row += 1
526
 
 
527
  for row in range(32, current_row):
528
  for col in range(1, 3):
529
  cell = ws.cell(row=row, column=col)
 
537
  return None, f"Error saving results: {str(e)}"
538
 
539
  def _write_result_to_cells(self, ws, result, cols, row):
 
540
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
541
 
542
  value_mapping = {
 
554
  cell.alignment = center_alignment
555
 
556
  def format_results(self):
 
557
  if not self.results:
558
  return "No measurements yet"
559
  df = pd.DataFrame(self.results)
 
562
  return df.to_string(index=False)
563
 
564
  def add_zero_row(self, image):
 
 
 
565
  self.results.append({
566
  'Area (mm²)': '0.000',
567
  'Mean': '0.000',
 
573
  return image, self.format_results()
574
 
575
  def add_two_zero_rows(self, image):
 
 
 
576
  for _ in range(2):
577
  self.results.append({
578
  'Area (mm²)': '0.000',
 
585
  return image, self.format_results()
586
 
587
  def undo_last(self, image):
588
+ if not self.results: # لو مفيش نتائج أصلاً
589
+ return self.update_display(), self.format_results()
590
+
591
+ last_result = self.results[-1]
592
+ # نتحقق إذا كان آخر إجراء قياس حقيقي أم صف صفري
593
+ is_measurement = last_result['Point'] != '(0, 0)'
594
+
595
+ # نمسح آخر نتيجة
596
+ self.results.pop()
597
+
598
+ # لو كان قياس حقيقي، نمسح العلامة المقابلة له
599
+ if is_measurement and self.marks:
600
  self.marks.pop()
601
+
602
  return self.update_display(), self.format_results()
603
 
604
 
 
633
  elem_id="image_display"
634
  )
635
 
 
636
  with gr.Row():
637
  zero_btn = gr.Button("Add Zero Row")
638
  zero2_btn = gr.Button("Add Two Zero Rows")
 
663
  output_path = "analysis_results_formatted.xlsx"
664
  return analyzer.save_formatted_results(output_path)
665
 
 
666
  file_input.change(
667
  fn=analyzer.load_dicom,
668
  inputs=file_input,
 
711
  outputs=image_display
712
  )
713
 
 
714
  zero_btn.click(
715
  fn=analyzer.add_zero_row,
716
  inputs=image_display,
 
739
  outputs=[file_output, results_display]
740
  )
741
 
 
742
  js = """
743
  <script>
744
  document.addEventListener('keydown', function(e) {
 
781
  print(f"Error launching application: {str(e)}")
782
  logger.error(f"Error launching application: {str(e)}")
783
  logger.error(traceback.format_exc())
784
+ raise e