HeshamAI commited on
Commit
df49e9f
·
verified ·
1 Parent(s): c3d845d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -85
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")
@@ -175,12 +170,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':
@@ -196,12 +187,7 @@ class DicomAnalyzer:
196
  except Exception as e:
197
  print(f"Error handling keyboard input: {str(e)}")
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,41 +319,7 @@ 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.
345
- """
346
- try:
347
- base_col = col_group[1] # Mean column
348
- std_col = col_group[2] # StdDev column
349
-
350
- row1, row2 = row_pair
351
-
352
- # SNR formula
353
- formula1 = f"=IFERROR({base_col}{row1}/{std_col}{row1},\"\")"
354
- formula_col = get_column_letter(column_index_from_string(col_group[-1]) + 1)
355
- cell1 = ws[f"{formula_col}{row1}"]
356
- cell1.value = formula1
357
- cell1.font = red_font
358
- cell1.alignment = openpyxl.styles.Alignment(horizontal='center')
359
-
360
- # CNR formula
361
- formula2 = f"=IFERROR(({base_col}{row1}-{base_col}{row2})/{std_col}{row2},\"\")"
362
- cell2 = ws[f"{formula_col}{row2}"]
363
- cell2.value = formula2
364
- cell2.font = red_font
365
- cell2.alignment = openpyxl.styles.Alignment(horizontal='center')
366
-
367
- logger.debug(f"Added formulas for rows {row1},{row2} in column {formula_col}")
368
- except Exception as e:
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 +329,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 +357,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 +373,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,7 +390,6 @@ 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):
@@ -457,10 +397,8 @@ class DicomAnalyzer:
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 +409,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 +424,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 +467,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 +481,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 +488,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 +496,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 +509,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 +526,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 +534,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 +545,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 +557,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 +605,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 +635,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 +683,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 +711,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) {
 
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")
 
170
  return self.update_display()
171
 
172
  def handle_keyboard(self, key):
 
 
 
173
  try:
174
  print(f"Handling key press: {key}")
 
175
  pan_amount = int(10 * self.zoom_factor)
176
 
177
  if key == 'ArrowLeft':
 
187
  except Exception as e:
188
  print(f"Error handling keyboard input: {str(e)}")
189
  return self.display_image
 
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 save_formatted_results(self, output_path):
 
 
 
 
323
  try:
324
  if not self.results:
325
  return None, "No results to save"
 
329
  red_font = openpyxl.styles.Font(color="FF0000")
330
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
331
 
 
332
  headers = ['Area', 'Mean', 'StdDev', 'Min', 'Max']
333
 
334
  column_groups = [
 
357
  '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)'
358
  ]
359
 
 
360
  for i, size in enumerate(phantom_sizes):
361
  header_cell = ws.cell(row=row_pairs[i][0]-1, column=1, value=size)
362
  header_cell.font = red_font
363
  header_cell.alignment = center_alignment
364
 
 
365
  result_idx = 0
366
  current_col_group = 0
367
  current_row_pair = 0
 
373
  cols = column_groups[current_col_group]
374
  rows = row_pairs[current_row_pair]
375
 
 
376
  if result_idx < len(self.results):
377
  result = self.results[result_idx]
378
  self._write_result_to_cells(ws, result, cols, rows[0])
379
  result_idx += 1
380
 
 
381
  if result_idx < len(self.results):
382
  result = self.results[result_idx]
383
  self._write_result_to_cells(ws, result, cols, rows[1])
384
  result_idx += 1
385
 
 
386
  self.add_formulas_to_template(ws, rows, cols, red_font)
387
 
388
  current_col_group += 1
 
390
  current_col_group = 0
391
  current_row_pair += 1
392
 
 
393
  for cols in column_groups:
394
  for col in cols:
395
  for row in range(2, 31):
 
397
  if cell.value is not None:
398
  cell.alignment = center_alignment
399
 
400
+ # StdDev Averages
401
  current_row = 32
 
 
402
  stddev_header = ws.cell(row=current_row, column=1, value="StdDev Averages")
403
  stddev_header.font = red_font
404
  stddev_header.alignment = center_alignment
 
409
  stddev_values = []
410
 
411
  for cols in column_groups:
412
+ stddev_col = cols[2]
413
  cell_value = ws[f"{stddev_col}{row_number}"].value
414
  if cell_value not in [0, None, '']:
415
  stddev_values.append(float(cell_value))
 
424
  avg_cell.alignment = center_alignment
425
  current_row += 1
426
 
427
+ # Mean Averages
428
  current_row += 2
429
+ mean_header = ws.cell(row=current_row, column=1, value="Mean Averages")
430
+ mean_header.font = red_font
431
+ mean_header.alignment = center_alignment
432
+ current_row += 1
433
 
434
+ for i, size in enumerate(phantom_sizes):
435
+ row_number = row_pairs[i][0] # نأخذ الصف الأول فقط
436
+ mean_values = []
437
+
438
+ for cols in column_groups:
439
+ mean_col = cols[1] # The Mean column
440
+ cell_value = ws[f"{mean_col}{row_number}"].value
441
+ if cell_value not in [0, None, '']:
442
+ mean_values.append(float(cell_value))
443
+
444
+ size_cell = ws.cell(row=current_row, column=1, value=size)
445
+ size_cell.alignment = center_alignment
446
+
447
+ if mean_values:
448
+ avg_mean = sum(mean_values) / len(mean_values)
449
+ avg_cell = ws.cell(row=current_row, column=2, value=avg_mean)
450
+ avg_cell.number_format = '0.000'
451
+ avg_cell.alignment = center_alignment
452
+ current_row += 1
453
+
454
+ current_row += 2
455
+
456
+ # CNR Averages
457
  cnr_header = ws.cell(row=current_row, column=1, value="CNR Averages")
458
  cnr_header.font = red_font
459
  cnr_header.alignment = center_alignment
 
467
  formula_col = get_column_letter(column_index_from_string(cols[-1]) + 1)
468
  cnr_cell_ref = f"{formula_col}{row_number}"
469
 
 
470
  mean_col = cols[1]
471
  std_col = cols[2]
472
 
 
481
  except:
482
  mean1_val, mean2_val, std2_val = 0, 0, 0
483
 
 
484
  if not (mean1_val == 0 and mean2_val == 0 and std2_val == 0):
485
  cnr_cells.append(cnr_cell_ref)
486
 
 
488
  size_cell.alignment = center_alignment
489
 
490
  if cnr_cells:
 
491
  average_formula = f'=IFERROR(AVERAGE({",".join(cnr_cells)}), "")'
 
492
  avg_cell = ws.cell(row=current_row, column=2)
493
  avg_cell.value = average_formula
494
  avg_cell.number_format = '0.000'
 
496
 
497
  current_row += 1
498
 
 
499
  for row in range(32, current_row):
500
  for col in range(1, 3):
501
  cell = ws.cell(row=row, column=col)
 
509
  return None, f"Error saving results: {str(e)}"
510
 
511
  def _write_result_to_cells(self, ws, result, cols, row):
 
512
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
513
 
514
  value_mapping = {
 
526
  cell.alignment = center_alignment
527
 
528
  def format_results(self):
 
529
  if not self.results:
530
  return "No measurements yet"
531
  df = pd.DataFrame(self.results)
 
534
  return df.to_string(index=False)
535
 
536
  def add_zero_row(self, image):
 
 
 
537
  self.results.append({
538
  'Area (mm²)': '0.000',
539
  'Mean': '0.000',
 
545
  return image, self.format_results()
546
 
547
  def add_two_zero_rows(self, image):
 
 
 
548
  for _ in range(2):
549
  self.results.append({
550
  'Area (mm²)': '0.000',
 
557
  return image, self.format_results()
558
 
559
  def undo_last(self, image):
560
+ if not self.results: # لو مفيش نتائج أصلاً
561
+ return self.update_display(), self.format_results()
562
+
563
+ last_result = self.results[-1]
564
+ # نتحقق إذا كان آخر إجراء قياس حقيقي أم صف صفري
565
+ is_measurement = last_result['Point'] != '(0, 0)'
566
+
567
+ # نمسح آخر نتيجة
568
+ self.results.pop()
569
+
570
+ # لو كان قياس حقيقي، نمسح العلامة المقابلة له
571
+ if is_measurement and self.marks:
572
  self.marks.pop()
573
+
574
  return self.update_display(), self.format_results()
575
 
576
 
 
605
  elem_id="image_display"
606
  )
607
 
 
608
  with gr.Row():
609
  zero_btn = gr.Button("Add Zero Row")
610
  zero2_btn = gr.Button("Add Two Zero Rows")
 
635
  output_path = "analysis_results_formatted.xlsx"
636
  return analyzer.save_formatted_results(output_path)
637
 
 
638
  file_input.change(
639
  fn=analyzer.load_dicom,
640
  inputs=file_input,
 
683
  outputs=image_display
684
  )
685
 
 
686
  zero_btn.click(
687
  fn=analyzer.add_zero_row,
688
  inputs=image_display,
 
711
  outputs=[file_output, results_display]
712
  )
713
 
 
714
  js = """
715
  <script>
716
  document.addEventListener('keydown', function(e) {