HeshamAI commited on
Commit
2d893a6
·
verified ·
1 Parent(s): 195e20d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -67
app.py CHANGED
@@ -45,42 +45,6 @@ def debug_decorator(func):
45
  logger.debug(f"Execution time: {end_time - start_time:.4f} seconds")
46
  return wrapper
47
 
48
- def save_results(self):
49
- """
50
- Basic save function for raw results with improved error handling and logging
51
- """
52
- try:
53
- if not self.results:
54
- logger.warning("Attempted to save with no results")
55
- return None, "No results to save"
56
-
57
- df = pd.DataFrame(self.results)
58
- columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
59
- df = df[columns_order]
60
-
61
- timestamp = time.strftime("%Y%m%d_%H%M%S")
62
- output_file = f"analysis_results_{timestamp}.xlsx"
63
-
64
- with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
65
- df.to_excel(writer, index=False, sheet_name='Results')
66
-
67
- worksheet = writer.sheets['Results']
68
- for idx, col in enumerate(df.columns):
69
- max_length = max(
70
- df[col].astype(str).apply(len).max(),
71
- len(str(col))
72
- ) + 2
73
- worksheet.column_dimensions[get_column_letter(idx + 1)].width = max_length
74
-
75
- logger.info(f"Results saved successfully to {output_file}")
76
- return output_file, f"Results saved successfully to {output_file}"
77
-
78
- except Exception as e:
79
- error_msg = f"Error saving results: {str(e)}"
80
- logger.error(error_msg)
81
- logger.error(traceback.format_exc())
82
- return None, error_msg
83
-
84
 
85
  class DicomAnalyzer:
86
  def __init__(self):
@@ -99,6 +63,43 @@ class DicomAnalyzer:
99
  self.max_pan_y = 0
100
  self.CIRCLE_COLOR = (0, 255, 255) # BGR Yellow
101
  print("DicomAnalyzer initialized...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  def reset_all(self, image):
103
  self.results = []
104
  self.marks = []
@@ -198,8 +199,11 @@ class DicomAnalyzer:
198
  new_height = int(height * self.zoom_factor)
199
  new_width = int(width * self.zoom_factor)
200
 
201
- zoomed = cv2.resize(self.original_display, (new_width, new_height),
202
- interpolation=cv2.INTER_CUBIC)
 
 
 
203
 
204
  zoomed_bgr = cv2.cvtColor(zoomed, cv2.COLOR_RGB2BGR)
205
 
@@ -208,24 +212,28 @@ class DicomAnalyzer:
208
  zoomed_y = int(y * self.zoom_factor)
209
  zoomed_radius = int((diameter/2.0) * self.zoom_factor)
210
 
211
- cv2.circle(zoomed_bgr,
212
- (zoomed_x, zoomed_y),
213
- zoomed_radius,
214
- self.CIRCLE_COLOR,
215
- 1,
216
- lineType=cv2.LINE_AA)
 
 
217
 
218
  num_points = 8
219
  for i in range(num_points):
220
  angle = 2 * np.pi * i / num_points
221
  point_x = int(zoomed_x + zoomed_radius * np.cos(angle))
222
  point_y = int(zoomed_y + zoomed_radius * np.sin(angle))
223
- cv2.circle(zoomed_bgr,
224
- (point_x, point_y),
225
- 1,
226
- self.CIRCLE_COLOR,
227
- -1,
228
- lineType=cv2.LINE_AA)
 
 
229
 
230
  zoomed = cv2.cvtColor(zoomed_bgr, cv2.COLOR_BGR2RGB)
231
 
@@ -243,6 +251,7 @@ class DicomAnalyzer:
243
  except Exception as e:
244
  print(f"Error updating display: {str(e)}")
245
  return self.original_display
 
246
  def analyze_roi(self, evt: gr.SelectData):
247
  try:
248
  if self.current_image is None:
@@ -318,7 +327,7 @@ class DicomAnalyzer:
318
 
319
  row1, row2 = row_pair
320
 
321
- # SNR Formula for first row with error handling
322
  formula1 = f"=IFERROR({base_col}{row1}/{std_col}{row1},\"\")"
323
  formula_col = get_column_letter(column_index_from_string(col_group[-1]) + 1)
324
  cell1 = ws[f"{formula_col}{row1}"]
@@ -326,7 +335,7 @@ class DicomAnalyzer:
326
  cell1.font = red_font
327
  cell1.alignment = openpyxl.styles.Alignment(horizontal='center')
328
 
329
- # CNR Formula for second row with error handling
330
  formula2 = f"=IFERROR(({base_col}{row1}-{base_col}{row2})/{std_col}{row2},\"\")"
331
  cell2 = ws[f"{formula_col}{row2}"]
332
  cell2.value = formula2
@@ -372,8 +381,10 @@ class DicomAnalyzer:
372
  (17, 18), (20, 21), (23, 24), (26, 27), (29, 30)
373
  ]
374
 
375
- phantom_sizes = ['(7mm)', '(6.5mm)', '(6mm)', '(5.5mm)', '(5mm)',
376
- '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)']
 
 
377
 
378
  for i, size in enumerate(phantom_sizes):
379
  header_cell = ws.cell(row=row_pairs[i][0]-1, column=1, value=size)
@@ -425,15 +436,15 @@ class DicomAnalyzer:
425
  stddev_header.alignment = center_alignment
426
  current_row += 1
427
 
428
- # Calculate StdDev averages for each row pair
429
  for i, size in enumerate(phantom_sizes):
430
- row_number = row_pairs[i][0] # First row only
431
  stddev_values = []
432
 
433
  for cols in column_groups:
434
  stddev_col = cols[2] # StdDev column
435
  cell_value = ws[f"{stddev_col}{row_number}"].value
436
- if cell_value not in [0, None, '']: # Ignore zeros and empty values
437
  stddev_values.append(float(cell_value))
438
 
439
  if stddev_values:
@@ -454,23 +465,22 @@ class DicomAnalyzer:
454
  cnr_header.alignment = center_alignment
455
  current_row += 1
456
 
457
- # Calculate CNR averages for each row pair
458
  for i, size in enumerate(phantom_sizes):
459
- row_number = row_pairs[i][1] # Second row for CNR
460
  valid_cnr_refs = []
461
 
462
- # Get all CNR cells for this row
463
  for cols in column_groups:
464
  formula_col = get_column_letter(column_index_from_string(cols[-1]) + 1)
465
  mean_col = cols[1]
466
  std_col = cols[2]
467
 
468
- # Check values used in CNR calculation
469
  mean1 = ws[f"{mean_col}{row_pairs[i][0]}"].value
470
  mean2 = ws[f"{mean_col}{row_pairs[i][1]}"].value
471
  std2 = ws[f"{std_col}{row_pairs[i][1]}"].value
472
 
473
- # Convert values to float if they're strings
474
  try:
475
  mean1 = float(mean1) if mean1 not in [None, ''] else 0
476
  mean2 = float(mean2) if mean2 not in [None, ''] else 0
@@ -478,16 +488,14 @@ class DicomAnalyzer:
478
  except (ValueError, TypeError):
479
  continue
480
 
481
- # Only skip if ALL values are zero
482
  if not (mean1 == 0 and mean2 == 0 and std2 == 0):
483
  valid_cnr_refs.append(f"{formula_col}{row_number}")
484
 
485
- # Add row to the averages table
486
  size_cell = ws.cell(row=current_row, column=1, value=size)
487
  size_cell.alignment = center_alignment
488
 
489
  if valid_cnr_refs:
490
- # Create array formula for average that handles errors
491
  refs = ",".join(valid_cnr_refs)
492
  array_formula = f'=IFERROR(AVERAGE(IF(ISNUMBER({refs}),{refs})),"")'
493
 
@@ -510,6 +518,7 @@ class DicomAnalyzer:
510
  except Exception as e:
511
  logger.error(f"Error saving formatted results: {str(e)}")
512
  return None, f"Error saving results: {str(e)}"
 
513
  def _write_result_to_cells(self, ws, result, cols, row):
514
  """Helper method to write a single result to worksheet cells"""
515
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
@@ -565,6 +574,7 @@ class DicomAnalyzer:
565
  self.marks.pop()
566
  return self.update_display(), self.format_results()
567
 
 
568
  def create_interface():
569
  print("Creating interface...")
570
  analyzer = DicomAnalyzer()
@@ -615,7 +625,7 @@ def create_interface():
615
  - Use Reset All to clear all measurements
616
  - Save Formatted Results will create Excel file with formulas
617
  """)
618
-
619
  def update_diameter(x):
620
  analyzer.circle_diameter = float(x)
621
  print(f"Diameter updated to: {x}")
@@ -739,4 +749,4 @@ if __name__ == "__main__":
739
  print(f"Error launching application: {str(e)}")
740
  logger.error(f"Error launching application: {str(e)}")
741
  logger.error(traceback.format_exc())
742
- raise e
 
45
  logger.debug(f"Execution time: {end_time - start_time:.4f} seconds")
46
  return wrapper
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  class DicomAnalyzer:
50
  def __init__(self):
 
63
  self.max_pan_y = 0
64
  self.CIRCLE_COLOR = (0, 255, 255) # BGR Yellow
65
  print("DicomAnalyzer initialized...")
66
+
67
+ def save_results(self):
68
+ """
69
+ Basic save function for raw results with improved error handling and logging
70
+ """
71
+ try:
72
+ if not self.results:
73
+ logger.warning("Attempted to save with no results")
74
+ return None, "No results to save"
75
+
76
+ df = pd.DataFrame(self.results)
77
+ columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
78
+ df = df[columns_order]
79
+
80
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
81
+ output_file = f"analysis_results_{timestamp}.xlsx"
82
+
83
+ with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
84
+ df.to_excel(writer, index=False, sheet_name='Results')
85
+
86
+ worksheet = writer.sheets['Results']
87
+ for idx, col in enumerate(df.columns):
88
+ max_length = max(
89
+ df[col].astype(str).apply(len).max(),
90
+ len(str(col))
91
+ ) + 2
92
+ worksheet.column_dimensions[get_column_letter(idx + 1)].width = max_length
93
+
94
+ logger.info(f"Results saved successfully to {output_file}")
95
+ return output_file, f"Results saved successfully to {output_file}"
96
+
97
+ except Exception as e:
98
+ error_msg = f"Error saving results: {str(e)}"
99
+ logger.error(error_msg)
100
+ logger.error(traceback.format_exc())
101
+ return None, error_msg
102
+
103
  def reset_all(self, image):
104
  self.results = []
105
  self.marks = []
 
199
  new_height = int(height * self.zoom_factor)
200
  new_width = int(width * self.zoom_factor)
201
 
202
+ zoomed = cv2.resize(
203
+ self.original_display,
204
+ (new_width, new_height),
205
+ interpolation=cv2.INTER_CUBIC
206
+ )
207
 
208
  zoomed_bgr = cv2.cvtColor(zoomed, cv2.COLOR_RGB2BGR)
209
 
 
212
  zoomed_y = int(y * self.zoom_factor)
213
  zoomed_radius = int((diameter/2.0) * self.zoom_factor)
214
 
215
+ cv2.circle(
216
+ zoomed_bgr,
217
+ (zoomed_x, zoomed_y),
218
+ zoomed_radius,
219
+ self.CIRCLE_COLOR,
220
+ 1,
221
+ lineType=cv2.LINE_AA
222
+ )
223
 
224
  num_points = 8
225
  for i in range(num_points):
226
  angle = 2 * np.pi * i / num_points
227
  point_x = int(zoomed_x + zoomed_radius * np.cos(angle))
228
  point_y = int(zoomed_y + zoomed_radius * np.sin(angle))
229
+ cv2.circle(
230
+ zoomed_bgr,
231
+ (point_x, point_y),
232
+ 1,
233
+ self.CIRCLE_COLOR,
234
+ -1,
235
+ lineType=cv2.LINE_AA
236
+ )
237
 
238
  zoomed = cv2.cvtColor(zoomed_bgr, cv2.COLOR_BGR2RGB)
239
 
 
251
  except Exception as e:
252
  print(f"Error updating display: {str(e)}")
253
  return self.original_display
254
+
255
  def analyze_roi(self, evt: gr.SelectData):
256
  try:
257
  if self.current_image is None:
 
327
 
328
  row1, row2 = row_pair
329
 
330
+ # SNR Formula for the first row with IFERROR
331
  formula1 = f"=IFERROR({base_col}{row1}/{std_col}{row1},\"\")"
332
  formula_col = get_column_letter(column_index_from_string(col_group[-1]) + 1)
333
  cell1 = ws[f"{formula_col}{row1}"]
 
335
  cell1.font = red_font
336
  cell1.alignment = openpyxl.styles.Alignment(horizontal='center')
337
 
338
+ # CNR Formula for the second row with IFERROR
339
  formula2 = f"=IFERROR(({base_col}{row1}-{base_col}{row2})/{std_col}{row2},\"\")"
340
  cell2 = ws[f"{formula_col}{row2}"]
341
  cell2.value = formula2
 
381
  (17, 18), (20, 21), (23, 24), (26, 27), (29, 30)
382
  ]
383
 
384
+ phantom_sizes = [
385
+ '(7mm)', '(6.5mm)', '(6mm)', '(5.5mm)', '(5mm)',
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)
 
436
  stddev_header.alignment = center_alignment
437
  current_row += 1
438
 
439
+ # Calculate StdDev averages for each row pair (top row only)
440
  for i, size in enumerate(phantom_sizes):
441
+ row_number = row_pairs[i][0] # First row
442
  stddev_values = []
443
 
444
  for cols in column_groups:
445
  stddev_col = cols[2] # StdDev column
446
  cell_value = ws[f"{stddev_col}{row_number}"].value
447
+ if cell_value not in [0, None, '']: # Ignore zeros and empty
448
  stddev_values.append(float(cell_value))
449
 
450
  if stddev_values:
 
465
  cnr_header.alignment = center_alignment
466
  current_row += 1
467
 
468
+ # Calculate CNR averages for each row pair (second row)
469
  for i, size in enumerate(phantom_sizes):
470
+ row_number = row_pairs[i][1] # second row for CNR
471
  valid_cnr_refs = []
472
 
473
+ # Build references only if Mean and StdDev are not all zero
474
  for cols in column_groups:
475
  formula_col = get_column_letter(column_index_from_string(cols[-1]) + 1)
476
  mean_col = cols[1]
477
  std_col = cols[2]
478
 
 
479
  mean1 = ws[f"{mean_col}{row_pairs[i][0]}"].value
480
  mean2 = ws[f"{mean_col}{row_pairs[i][1]}"].value
481
  std2 = ws[f"{std_col}{row_pairs[i][1]}"].value
482
 
483
+ # Convert potential string values to float
484
  try:
485
  mean1 = float(mean1) if mean1 not in [None, ''] else 0
486
  mean2 = float(mean2) if mean2 not in [None, ''] else 0
 
488
  except (ValueError, TypeError):
489
  continue
490
 
491
+ # If not all zero, we consider the cell for average
492
  if not (mean1 == 0 and mean2 == 0 and std2 == 0):
493
  valid_cnr_refs.append(f"{formula_col}{row_number}")
494
 
 
495
  size_cell = ws.cell(row=current_row, column=1, value=size)
496
  size_cell.alignment = center_alignment
497
 
498
  if valid_cnr_refs:
 
499
  refs = ",".join(valid_cnr_refs)
500
  array_formula = f'=IFERROR(AVERAGE(IF(ISNUMBER({refs}),{refs})),"")'
501
 
 
518
  except Exception as e:
519
  logger.error(f"Error saving formatted results: {str(e)}")
520
  return None, f"Error saving results: {str(e)}"
521
+
522
  def _write_result_to_cells(self, ws, result, cols, row):
523
  """Helper method to write a single result to worksheet cells"""
524
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
 
574
  self.marks.pop()
575
  return self.update_display(), self.format_results()
576
 
577
+
578
  def create_interface():
579
  print("Creating interface...")
580
  analyzer = DicomAnalyzer()
 
625
  - Use Reset All to clear all measurements
626
  - Save Formatted Results will create Excel file with formulas
627
  """)
628
+
629
  def update_diameter(x):
630
  analyzer.circle_diameter = float(x)
631
  print(f"Diameter updated to: {x}")
 
749
  print(f"Error launching application: {str(e)}")
750
  logger.error(f"Error launching application: {str(e)}")
751
  logger.error(traceback.format_exc())
752
+ raise e