HeshamAI commited on
Commit
140990e
·
verified ·
1 Parent(s): 73270a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +124 -112
app.py CHANGED
@@ -324,16 +324,17 @@ class DicomAnalyzer:
324
  def add_formulas_to_template(self, ws, row_pair, col_group, red_font):
325
  """
326
  Inserts SNR (first row) and CNR (second row) formulas with IFERROR.
 
327
  """
328
  try:
329
  base_col = col_group[1] # Mean column
330
- std_col = col_group[2] # StdDev column
331
 
332
  row1, row2 = row_pair
333
 
334
  # SNR formula in row1
335
- formula1 = f"=IFERROR({base_col}{row1}/{std_col}{row1},\"\")"
336
  formula_col = get_column_letter(column_index_from_string(col_group[-1]) + 1)
 
337
  cell1 = ws[f"{formula_col}{row1}"]
338
  cell1.value = formula1
339
  cell1.font = red_font
@@ -350,9 +351,6 @@ class DicomAnalyzer:
350
  except Exception as e:
351
  logger.error(f"Error adding formulas: {str(e)}")
352
 
353
- ########################################################################
354
- # التعديل الوحيد: في تجميع الـCNR نقرأ من خلية المعادلة بدلا من (m1 - m2)/std2
355
- ########################################################################
356
  def save_formatted_results(self, output_path):
357
  try:
358
  if not self.results:
@@ -364,6 +362,7 @@ class DicomAnalyzer:
364
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
365
 
366
  headers = ['Area', 'Mean', 'StdDev', 'Min', 'Max']
 
367
  column_groups = [
368
  ('B', 'C', 'D', 'E', 'F'), ('H', 'I', 'J', 'K', 'L'),
369
  ('N', 'O', 'P', 'Q', 'R'), ('T', 'U', 'V', 'W', 'X'),
@@ -373,14 +372,15 @@ class DicomAnalyzer:
373
  ('BJ', 'BK', 'BL', 'BM', 'BN'), ('BP', 'BQ', 'BR', 'BS', 'BT'),
374
  ('BV', 'BW', 'BX', 'BY', 'BZ')
375
  ]
376
-
377
- # Write table headers in row1
378
  for cols in column_groups:
379
  for i, header in enumerate(headers):
380
  cell = ws[f"{cols[i]}1"]
381
  cell.value = header
382
  cell.alignment = center_alignment
383
 
 
384
  row_pairs = [
385
  (2, 3), (5, 6), (8, 9), (11, 12), (14, 15),
386
  (17, 18), (20, 21), (23, 24), (26, 27), (29, 30)
@@ -391,13 +391,13 @@ class DicomAnalyzer:
391
  '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)'
392
  ]
393
 
394
- # Phantom labels in column A
395
  for i, size in enumerate(phantom_sizes):
396
  header_cell = ws.cell(row=row_pairs[i][0]-1, column=1, value=size)
397
  header_cell.font = red_font
398
  header_cell.alignment = center_alignment
399
 
400
- # Write ROI results into the defined rows/columns
401
  result_idx = 0
402
  current_col_group = 0
403
  current_row_pair = 0
@@ -409,16 +409,19 @@ class DicomAnalyzer:
409
  cols = column_groups[current_col_group]
410
  rows = row_pairs[current_row_pair]
411
 
 
412
  if result_idx < len(self.results):
413
  result = self.results[result_idx]
414
  self._write_result_to_cells(ws, result, cols, rows[0])
415
  result_idx += 1
416
 
 
417
  if result_idx < len(self.results):
418
  result = self.results[result_idx]
419
  self._write_result_to_cells(ws, result, cols, rows[1])
420
  result_idx += 1
421
 
 
422
  self.add_formulas_to_template(ws, rows, cols, red_font)
423
 
424
  current_col_group += 1
@@ -426,7 +429,7 @@ class DicomAnalyzer:
426
  current_col_group = 0
427
  current_row_pair += 1
428
 
429
- # Align cells for existing data
430
  for cols in column_groups:
431
  for col in cols:
432
  for row in range(2, 31):
@@ -434,122 +437,131 @@ class DicomAnalyzer:
434
  if cell.value is not None:
435
  cell.alignment = center_alignment
436
 
437
- # Now build the "1-AVG" table from row 35
438
- start_row = 35
439
- ws['C35'] = "1-AVG"
440
- ws['C35'].alignment = center_alignment
441
-
442
- ws.merge_cells('D35:E35')
443
- ws.merge_cells('F35:G35')
444
- ws.merge_cells('H35:I35')
445
-
446
- avg_headers = {
447
- 'D35': 'AVG MEAN',
448
- 'F35': 'AVG STDDEV',
449
- 'H35': 'AVG CNR'
450
- }
451
- for c_ref, text_val in avg_headers.items():
452
- ws[c_ref] = text_val
453
- ws[c_ref].font = red_font
454
- ws[c_ref].alignment = center_alignment
455
-
456
- phantom_sizes2 = [
457
- '(7.0mm)', '(6.5mm)', '(6.0mm)', '(5.5mm)', '(5.0mm)',
458
- '(4.5mm)', '(4.0mm)', '(3.5mm)', '(3.0mm)', '(2.5mm)'
459
- ]
460
 
461
- # For each phantom row, gather Mean, STD, and read the CNR from the formula column
462
- for i, p_size in enumerate(phantom_sizes2):
463
- row = start_row + i + 1 # rows 36..45
464
 
465
- ws.merge_cells(f'D{row}:E{row}')
466
- ws.merge_cells(f'F{row}:G{row}')
467
- ws.merge_cells(f'H{row}:I{row}')
 
 
468
 
469
- size_cell = ws[f'C{row}']
470
- size_cell.value = p_size
471
- size_cell.font = red_font
472
  size_cell.alignment = center_alignment
473
 
474
- if i >= len(row_pairs):
475
- continue
476
-
477
- (raw_row1, raw_row2) = row_pairs[i]
478
-
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  mean_values = []
480
- stddev_values = []
481
- cnr_values = []
482
 
483
- # بدل ما نحسب (m1 - m2)/std2 مباشرة
484
- # سنقرأ خلية المعادلة في row2 لكل مجموعة
485
- for group in column_groups:
486
- mean_col = group[1] # 'C' مثلاً
487
- std_col = group[2] # 'D' مثلاً
488
-
489
- # Read mean from row1 (if you want to average them)
490
- m1_val = ws[f"{mean_col}{raw_row1}"].value
491
- if m1_val not in [None,'']:
492
- try:
493
- mean_values.append(float(m1_val))
494
- except:
495
- pass
496
-
497
- # Read std from row1 (if you want to average them)
498
- std_val = ws[f"{std_col}{raw_row1}"].value
499
- if std_val not in [None,'']:
500
- try:
501
- stddev_values.append(float(std_val))
502
- except:
503
- pass
504
-
505
- # ---- الأهم: CNR يأتي من صيغة المعادلة (العمود الذي يلي group[-1]) في الصف الثاني ----
506
- formula_col_index = column_index_from_string(group[-1]) + 1
507
- formula_col = get_column_letter(formula_col_index)
508
- cnr_cell_value = ws[f"{formula_col}{raw_row2}"].value # حيث توجد =IFERROR((Mean1-Mean2)/Std2,"")
509
-
510
- if cnr_cell_value not in [None,'']:
511
- try:
512
- cnr_values.append(float(cnr_cell_value))
513
- except:
514
- pass
515
 
516
- # احسب المتوسطات
517
- final_mean = (sum(mean_values)/len(mean_values)) if mean_values else None
518
- final_std = (sum(stddev_values)/len(stddev_values)) if stddev_values else None
519
- final_cnr = (sum(cnr_values)/len(cnr_values)) if cnr_values else None
520
-
521
- # اكتبهم في الجدول
522
- if final_mean is not None:
523
- ws[f'D{row}'].value = final_mean
524
- ws[f'D{row}'].alignment = center_alignment
525
- ws[f'D{row}'].number_format = '0.0000'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
 
527
- if final_std is not None:
528
- ws[f'F{row}'].value = final_std
529
- ws[f'F{row}'].alignment = center_alignment
530
- ws[f'F{row}'].number_format = '0.0000'
 
 
 
 
 
 
531
 
532
- if final_cnr is not None:
533
- ws[f'H{row}'].value = final_cnr
534
- ws[f'H{row}'].alignment = center_alignment
535
- ws[f'H{row}'].number_format = '0.0000'
536
-
537
- # Border around C35..I45
538
- thin_side = openpyxl.styles.Side(style='thin')
539
- border = openpyxl.styles.Border(
540
- left=thin_side, right=thin_side, top=thin_side, bottom=thin_side
541
- )
542
- for r in range(35, 46):
543
- for c in ['C','D','E','F','G','H','I']:
544
- ws[f'{c}{r}'].border = border
545
 
546
- # Finally save
 
 
 
 
 
 
547
  wb.save(output_path)
548
  return output_path, f"Results saved successfully ({len(self.results)} measurements)"
 
549
  except Exception as e:
550
  logger.error(f"Error saving formatted results: {str(e)}")
551
  return None, f"Error saving results: {str(e)}"
552
- ########################################################################
553
 
554
  def _write_result_to_cells(self, ws, result, cols, row):
555
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
@@ -600,7 +612,7 @@ class DicomAnalyzer:
600
  return image, self.format_results()
601
 
602
  def undo_last(self, image):
603
- if not self.results:
604
  return self.update_display(), self.format_results()
605
 
606
  last_result = self.results[-1]
@@ -663,7 +675,7 @@ def create_interface():
663
  - Use Zoom In/Out buttons or Reset View to adjust zoom level.
664
  - Use Reset All to clear all measurements.
665
  - "Save Results": basic Excel with raw data.
666
- - "Save Formatted Results": Excel with advanced formatting & the 1-AVG table (including correct CNR averaging).
667
  """)
668
 
669
  def update_diameter(x):
 
324
  def add_formulas_to_template(self, ws, row_pair, col_group, red_font):
325
  """
326
  Inserts SNR (first row) and CNR (second row) formulas with IFERROR.
327
+ (Same approach: col_group[-1] is the last column, so the formula goes in the next col.)
328
  """
329
  try:
330
  base_col = col_group[1] # Mean column
331
+ std_col = col_group[2] # StdDev column
332
 
333
  row1, row2 = row_pair
334
 
335
  # SNR formula in row1
 
336
  formula_col = get_column_letter(column_index_from_string(col_group[-1]) + 1)
337
+ formula1 = f"=IFERROR({base_col}{row1}/{std_col}{row1},\"\")"
338
  cell1 = ws[f"{formula_col}{row1}"]
339
  cell1.value = formula1
340
  cell1.font = red_font
 
351
  except Exception as e:
352
  logger.error(f"Error adding formulas: {str(e)}")
353
 
 
 
 
354
  def save_formatted_results(self, output_path):
355
  try:
356
  if not self.results:
 
362
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
363
 
364
  headers = ['Area', 'Mean', 'StdDev', 'Min', 'Max']
365
+
366
  column_groups = [
367
  ('B', 'C', 'D', 'E', 'F'), ('H', 'I', 'J', 'K', 'L'),
368
  ('N', 'O', 'P', 'Q', 'R'), ('T', 'U', 'V', 'W', 'X'),
 
372
  ('BJ', 'BK', 'BL', 'BM', 'BN'), ('BP', 'BQ', 'BR', 'BS', 'BT'),
373
  ('BV', 'BW', 'BX', 'BY', 'BZ')
374
  ]
375
+
376
+ # Write headers in row1 for each column group
377
  for cols in column_groups:
378
  for i, header in enumerate(headers):
379
  cell = ws[f"{cols[i]}1"]
380
  cell.value = header
381
  cell.alignment = center_alignment
382
 
383
+ # row pairs for 10 phantoms, each has row1 and row2
384
  row_pairs = [
385
  (2, 3), (5, 6), (8, 9), (11, 12), (14, 15),
386
  (17, 18), (20, 21), (23, 24), (26, 27), (29, 30)
 
391
  '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)'
392
  ]
393
 
394
+ # Put phantom sizes in column A (above row1 for each pair)
395
  for i, size in enumerate(phantom_sizes):
396
  header_cell = ws.cell(row=row_pairs[i][0]-1, column=1, value=size)
397
  header_cell.font = red_font
398
  header_cell.alignment = center_alignment
399
 
400
+ # Fill data from self.results in these row_pairs/column_groups
401
  result_idx = 0
402
  current_col_group = 0
403
  current_row_pair = 0
 
409
  cols = column_groups[current_col_group]
410
  rows = row_pairs[current_row_pair]
411
 
412
+ # first measurement -> row1
413
  if result_idx < len(self.results):
414
  result = self.results[result_idx]
415
  self._write_result_to_cells(ws, result, cols, rows[0])
416
  result_idx += 1
417
 
418
+ # second measurement -> row2
419
  if result_idx < len(self.results):
420
  result = self.results[result_idx]
421
  self._write_result_to_cells(ws, result, cols, rows[1])
422
  result_idx += 1
423
 
424
+ # Insert SNR/CNR formulas in these 2 rows (mean, std dev columns to formula col)
425
  self.add_formulas_to_template(ws, rows, cols, red_font)
426
 
427
  current_col_group += 1
 
429
  current_col_group = 0
430
  current_row_pair += 1
431
 
432
+ # center alignment for existing cells (2..30 in all column groups)
433
  for cols in column_groups:
434
  for col in cols:
435
  for row in range(2, 31):
 
437
  if cell.value is not None:
438
  cell.alignment = center_alignment
439
 
440
+ # Now do the 3 sections: StdDev Averages, Mean Averages, CNR Averages
441
+
442
+ ##################
443
+ # 1) StdDev Averages
444
+ ##################
445
+ current_row = 32
446
+ stddev_header = ws.cell(row=current_row, column=1, value="StdDev Averages")
447
+ stddev_header.font = red_font
448
+ stddev_header.alignment = center_alignment
449
+ current_row += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
450
 
451
+ for i, size in enumerate(phantom_sizes):
452
+ row_number = row_pairs[i][0]
453
+ stddev_values = []
454
 
455
+ for cols in column_groups:
456
+ stddev_col = cols[2] # 'D','J','P'.. etc.
457
+ cell_value = ws[f"{stddev_col}{row_number}"].value
458
+ if cell_value not in [0, None, '']:
459
+ stddev_values.append(float(cell_value))
460
 
461
+ size_cell = ws.cell(row=current_row, column=1, value=size)
 
 
462
  size_cell.alignment = center_alignment
463
 
464
+ if stddev_values:
465
+ avg_stddev = sum(stddev_values) / len(stddev_values)
466
+ avg_cell = ws.cell(row=current_row, column=2, value=avg_stddev)
467
+ avg_cell.number_format = '0.000'
468
+ avg_cell.alignment = center_alignment
469
+ current_row += 1
470
+
471
+ ##################
472
+ # 2) Mean Averages
473
+ ##################
474
+ current_row += 2
475
+ mean_header = ws.cell(row=current_row, column=1, value="Mean Averages")
476
+ mean_header.font = red_font
477
+ mean_header.alignment = center_alignment
478
+ current_row += 1
479
+
480
+ for i, size in enumerate(phantom_sizes):
481
+ row_number = row_pairs[i][0] # we take the first row only (object)
482
  mean_values = []
 
 
483
 
484
+ for cols in column_groups:
485
+ mean_col = cols[1] # 'C','I','O'.. etc.
486
+ cell_value = ws[f"{mean_col}{row_number}"].value
487
+ if cell_value not in [0, None, '']:
488
+ mean_values.append(float(cell_value))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
 
490
+ size_cell = ws.cell(row=current_row, column=1, value=size)
491
+ size_cell.alignment = center_alignment
492
+
493
+ if mean_values:
494
+ avg_mean = sum(mean_values) / len(mean_values)
495
+ avg_cell = ws.cell(row=current_row, column=2, value=avg_mean)
496
+ avg_cell.number_format = '0.000'
497
+ avg_cell.alignment = center_alignment
498
+ current_row += 1
499
+
500
+ current_row += 2
501
+
502
+ ##################
503
+ # 3) CNR Averages (the snippet approach: build `=AVERAGE(...)` referencing each phantom's CNR cell)
504
+ ##################
505
+ cnr_header = ws.cell(row=current_row, column=1, value="CNR Averages")
506
+ cnr_header.font = red_font
507
+ cnr_header.alignment = center_alignment
508
+ current_row += 1
509
+
510
+ for i, size in enumerate(phantom_sizes):
511
+ row_number = row_pairs[i][1] # second row for each phantom
512
+ cnr_cells = []
513
+
514
+ # We'll collect references to the formula column for each column group and row2.
515
+ for cols in column_groups:
516
+ # formula col is after the last col in group (like 'F' -> 'G')
517
+ formula_col = get_column_letter(column_index_from_string(cols[-1]) + 1)
518
+ cnr_cell_ref = f"{formula_col}{row_number}"
519
+
520
+ # we also check if mean1,mean2,std2 are zero => skip or not. This is from the snippet logic.
521
+ mean_col = cols[1]
522
+ std_col = cols[2]
523
+
524
+ mean1_val = ws[f"{mean_col}{row_pairs[i][0]}"].value
525
+ mean2_val = ws[f"{mean_col}{row_pairs[i][1]}"].value
526
+ std2_val = ws[f"{std_col}{row_pairs[i][1]}"].value
527
+
528
+ try:
529
+ mean1_val = float(mean1_val) if mean1_val not in [None, ''] else 0
530
+ mean2_val = float(mean2_val) if mean2_val not in [None, ''] else 0
531
+ std2_val = float(std2_val) if std2_val not in [None, ''] else 0
532
+ except:
533
+ mean1_val, mean2_val, std2_val = 0, 0, 0
534
+
535
+ if not (mean1_val == 0 and mean2_val == 0 and std2_val == 0):
536
+ # i.e. valid ROI data => we add the formula cell reference to average later.
537
+ cnr_cells.append(cnr_cell_ref)
538
 
539
+ size_cell = ws.cell(row=current_row, column=1, value=size)
540
+ size_cell.alignment = center_alignment
541
+
542
+ if cnr_cells:
543
+ # create a new formula to average those CNR cells:
544
+ average_formula = f'=IFERROR(AVERAGE({",".join(cnr_cells)}), "")'
545
+ avg_cell = ws.cell(row=current_row, column=2)
546
+ avg_cell.value = average_formula
547
+ avg_cell.number_format = '0.000'
548
+ avg_cell.alignment = center_alignment
549
 
550
+ current_row += 1
 
 
 
 
 
 
 
 
 
 
 
 
551
 
552
+ # Finally, center-align from row 32.. up to current_row in columns 1..2
553
+ for row in range(32, current_row):
554
+ for col in range(1, 3):
555
+ cell = ws.cell(row=row, column=col)
556
+ cell.alignment = center_alignment
557
+
558
+ # Save workbook to disk
559
  wb.save(output_path)
560
  return output_path, f"Results saved successfully ({len(self.results)} measurements)"
561
+
562
  except Exception as e:
563
  logger.error(f"Error saving formatted results: {str(e)}")
564
  return None, f"Error saving results: {str(e)}"
 
565
 
566
  def _write_result_to_cells(self, ws, result, cols, row):
567
  center_alignment = openpyxl.styles.Alignment(horizontal='center')
 
612
  return image, self.format_results()
613
 
614
  def undo_last(self, image):
615
+ if not self.results: # لو مفيش نتائج أصلاً
616
  return self.update_display(), self.format_results()
617
 
618
  last_result = self.results[-1]
 
675
  - Use Zoom In/Out buttons or Reset View to adjust zoom level.
676
  - Use Reset All to clear all measurements.
677
  - "Save Results": basic Excel with raw data.
678
+ - "Save Formatted Results": Excel with advanced formatting & formulas (including average CNR).
679
  """)
680
 
681
  def update_diameter(x):