HeshamAI commited on
Commit
94cb640
·
verified ·
1 Parent(s): b153b7c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -74
app.py CHANGED
@@ -65,6 +65,13 @@ class DicomAnalyzer:
65
  self.CIRCLE_COLOR = (0, 255, 255) # BGR Yellow
66
  print("DicomAnalyzer initialized...")
67
 
 
 
 
 
 
 
 
68
  def load_dicom(self, file):
69
  try:
70
  if file is None:
@@ -91,15 +98,14 @@ class DicomAnalyzer:
91
  self.display_image = self.normalize_image(image)
92
  self.original_display = self.display_image.copy()
93
 
94
- # Reset view on new image
95
- self.reset_view()
96
  print("DICOM file loaded successfully")
97
 
98
  return self.display_image, "DICOM file loaded successfully"
99
  except Exception as e:
100
  print(f"Error loading DICOM file: {str(e)}")
101
  return None, f"Error loading DICOM file: {str(e)}"
102
-
103
  def normalize_image(self, image):
104
  try:
105
  normalized = cv2.normalize(
@@ -161,6 +167,7 @@ class DicomAnalyzer:
161
  except Exception as e:
162
  print(f"Error handling keyboard input: {str(e)}")
163
  return self.display_image
 
164
  def analyze_roi(self, evt: gr.SelectData):
165
  try:
166
  if self.current_image is None:
@@ -287,41 +294,38 @@ class DicomAnalyzer:
287
  print(f"Error updating display: {str(e)}")
288
  return self.original_display
289
 
290
- def format_results(self):
291
- if not self.results:
292
- return "No measurements yet"
293
- df = pd.DataFrame(self.results)
294
- columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
295
- df = df[columns_order]
296
- return df.to_string(index=False)
297
-
298
- def save_results(self):
299
  try:
300
- if not self.results:
301
- return None, "No results to save"
 
 
 
302
 
303
- df = pd.DataFrame(self.results)
304
- columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
305
- df = df[columns_order]
 
306
 
307
- temp_file = "analysis_results.xlsx"
308
- df.to_excel(temp_file, index=False)
 
309
 
310
- return temp_file, "Results saved successfully"
311
  except Exception as e:
312
- print(f"Error saving results: {str(e)}")
313
- return None, f"Error saving results: {str(e)}"
314
 
315
- def save_results_to_template(self, template_path, output_path):
316
  try:
317
  if not self.results:
318
  return None, "No results to save"
319
 
320
- # Load the template while preserving formulas
321
- wb = openpyxl.load_workbook(template_path, data_only=False)
322
  ws = wb.active
323
 
324
- # Define the column groups for horizontal saving (13 groups)
325
  column_groups = [
326
  ('B', 'C', 'D', 'E', 'F'), # First group
327
  ('H', 'I', 'J', 'K', 'L'), # Second group
@@ -338,7 +342,6 @@ class DicomAnalyzer:
338
  ('BV', 'BW', 'BX', 'BY', 'BZ') # Thirteenth group
339
  ]
340
 
341
- # Define row pairs
342
  row_pairs = [
343
  (2, 3), # 7mm
344
  (5, 6), # 6.5mm
@@ -352,6 +355,12 @@ class DicomAnalyzer:
352
  (29, 30) # 2.5mm
353
  ]
354
 
 
 
 
 
 
 
355
  # Process results
356
  result_idx = 0
357
  current_col_group = 0
@@ -361,60 +370,50 @@ class DicomAnalyzer:
361
  if current_row_pair >= len(row_pairs):
362
  break
363
 
364
- # Get current positions
365
  cols = column_groups[current_col_group]
366
  rows = row_pairs[current_row_pair]
367
 
368
- # Write first row if available
369
  if result_idx < len(self.results):
370
  result = self.results[result_idx]
371
- # Write Area
372
- ws[f"{cols[0]}{rows[0]}"] = float(result['Area (mm²)'])
373
- # Write Mean
374
- ws[f"{cols[1]}{rows[0]}"] = float(result['Mean'])
375
- # Write StdDev
376
- ws[f"{cols[2]}{rows[0]}"] = float(result['StdDev'])
377
- # Write Min
378
- ws[f"{cols[3]}{rows[0]}"] = float(result['Min'])
379
- # Write Max
380
- ws[f"{cols[4]}{rows[0]}"] = float(result['Max'])
381
  result_idx += 1
382
 
383
- # Write second row if available
384
  if result_idx < len(self.results):
385
  result = self.results[result_idx]
386
- # Write Area
387
- ws[f"{cols[0]}{rows[1]}"] = float(result['Area (mm²)'])
388
- # Write Mean
389
- ws[f"{cols[1]}{rows[1]}"] = float(result['Mean'])
390
- # Write StdDev
391
- ws[f"{cols[2]}{rows[1]}"] = float(result['StdDev'])
392
- # Write Min
393
- ws[f"{cols[3]}{rows[1]}"] = float(result['Min'])
394
- # Write Max
395
- ws[f"{cols[4]}{rows[1]}"] = float(result['Max'])
396
  result_idx += 1
397
 
398
- # Move to next column group
399
- current_col_group += 1
400
 
401
- # If we've used all 13 column groups, move to next row pair
402
  if current_col_group >= len(column_groups):
403
  current_col_group = 0
404
  current_row_pair += 1
405
 
406
- print(f"Processed results {result_idx-2}-{result_idx-1} in columns {cols[0]}-{cols[4]} rows {rows[0]}-{rows[1]}")
407
-
408
- # Save while preserving formulas
409
  wb.save(output_path)
410
- print(f"Saved {result_idx} results to template")
411
- return output_path, f"Results saved successfully in template format ({result_idx} measurements)"
412
 
413
  except Exception as e:
414
- print(f"Error saving results to template: {str(e)}")
415
- logger.error(f"Error saving results to template: {str(e)}")
416
- logger.error(traceback.format_exc())
417
- return None, f"Error saving results to template: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
 
419
  def add_blank_row(self, image):
420
  self.results.append({
@@ -454,7 +453,6 @@ def create_interface():
454
  with gr.Row():
455
  with gr.Column():
456
  file_input = gr.File(label="Upload DICOM file")
457
- template_input = gr.File(label="Upload Excel Template")
458
  diameter_slider = gr.Slider(
459
  minimum=1,
460
  maximum=20,
@@ -467,6 +465,7 @@ def create_interface():
467
  zoom_in_btn = gr.Button("Zoom In (+)")
468
  zoom_out_btn = gr.Button("Zoom Out (-)")
469
  reset_btn = gr.Button("Reset View")
 
470
 
471
  with gr.Column():
472
  image_display = gr.Image(
@@ -480,7 +479,7 @@ def create_interface():
480
  zero_btn = gr.Button("Add Zero Row")
481
  undo_btn = gr.Button("Undo Last")
482
  save_btn = gr.Button("Save Results")
483
- save_template_btn = gr.Button("Save to Template")
484
 
485
  results_display = gr.Textbox(label="Results", interactive=False)
486
  file_output = gr.File(label="Download Results")
@@ -491,7 +490,8 @@ def create_interface():
491
  - Use arrow keys to pan when zoomed in
492
  - Click points to measure
493
  - Use Zoom In/Out buttons or Reset View to adjust zoom level
494
- - Upload template and use Save to Template for formatted results
 
495
  """)
496
 
497
  def update_diameter(x):
@@ -499,12 +499,9 @@ def create_interface():
499
  print(f"Diameter updated to: {x}")
500
  return f"Diameter set to {x} pixels"
501
 
502
- def save_to_template(template_file):
503
- if template_file is None:
504
- return None, "No template file provided"
505
-
506
- output_path = "analysis_results_from_template.xlsx"
507
- return analyzer.save_results_to_template(template_file.name, output_path)
508
 
509
  # Event handlers
510
  file_input.change(
@@ -543,6 +540,13 @@ def create_interface():
543
  outputs=image_display
544
  )
545
 
 
 
 
 
 
 
 
546
  key_press.change(
547
  fn=analyzer.handle_keyboard,
548
  inputs=key_press,
@@ -572,9 +576,8 @@ def create_interface():
572
  outputs=[file_output, results_display]
573
  )
574
 
575
- save_template_btn.click(
576
- fn=save_to_template,
577
- inputs=[template_input],
578
  outputs=[file_output, results_display]
579
  )
580
 
 
65
  self.CIRCLE_COLOR = (0, 255, 255) # BGR Yellow
66
  print("DicomAnalyzer initialized...")
67
 
68
+ def reset_all(self, image):
69
+ """Reset all data and view settings"""
70
+ self.results = [] # Clear all results
71
+ self.marks = [] # Clear all marks
72
+ self.reset_view() # Reset view settings
73
+ return self.update_display(), "All data has been reset"
74
+
75
  def load_dicom(self, file):
76
  try:
77
  if file is None:
 
98
  self.display_image = self.normalize_image(image)
99
  self.original_display = self.display_image.copy()
100
 
101
+ # Reset all data when loading new image
102
+ self.reset_all(None)
103
  print("DICOM file loaded successfully")
104
 
105
  return self.display_image, "DICOM file loaded successfully"
106
  except Exception as e:
107
  print(f"Error loading DICOM file: {str(e)}")
108
  return None, f"Error loading DICOM file: {str(e)}"
 
109
  def normalize_image(self, image):
110
  try:
111
  normalized = cv2.normalize(
 
167
  except Exception as e:
168
  print(f"Error handling keyboard input: {str(e)}")
169
  return self.display_image
170
+
171
  def analyze_roi(self, evt: gr.SelectData):
172
  try:
173
  if self.current_image is None:
 
294
  print(f"Error updating display: {str(e)}")
295
  return self.original_display
296
 
297
+ def add_formulas_to_template(self, ws, row_pair, col_group):
298
+ """Add formulas for each row pair and column group"""
 
 
 
 
 
 
 
299
  try:
300
+ # Get base column for formulas
301
+ base_col = col_group[1] # Mean column
302
+ std_col = col_group[2] # StdDev column
303
+
304
+ row1, row2 = row_pair
305
 
306
+ # Formula for first row: =Mean/StdDev
307
+ formula1 = f"={base_col}{row1}/{std_col}{row1}"
308
+ formula_col = get_column_letter(column_index_from_string(col_group[-1]) + 1)
309
+ ws[f"{formula_col}{row1}"] = formula1
310
 
311
+ # Formula for second row: =(Mean1-Mean2)/StdDev2
312
+ formula2 = f"=({base_col}{row1}-{base_col}{row2})/{std_col}{row2}"
313
+ ws[f"{formula_col}{row2}"] = formula2
314
 
315
+ logger.debug(f"Added formulas for rows {row1},{row2} in column {formula_col}")
316
  except Exception as e:
317
+ logger.error(f"Error adding formulas: {str(e)}")
 
318
 
319
+ def save_formatted_results(self, output_path):
320
  try:
321
  if not self.results:
322
  return None, "No results to save"
323
 
324
+ # Create new workbook
325
+ wb = openpyxl.Workbook()
326
  ws = wb.active
327
 
328
+ # Define column groups and row pairs
329
  column_groups = [
330
  ('B', 'C', 'D', 'E', 'F'), # First group
331
  ('H', 'I', 'J', 'K', 'L'), # Second group
 
342
  ('BV', 'BW', 'BX', 'BY', 'BZ') # Thirteenth group
343
  ]
344
 
 
345
  row_pairs = [
346
  (2, 3), # 7mm
347
  (5, 6), # 6.5mm
 
355
  (29, 30) # 2.5mm
356
  ]
357
 
358
+ # Add headers
359
+ phantom_sizes = ['(7mm)', '(6.5mm)', '(6mm)', '(5.5mm)', '(5mm)',
360
+ '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)']
361
+ for i, size in enumerate(phantom_sizes):
362
+ ws.cell(row=row_pairs[i][0]-1, column=1, value=size)
363
+
364
  # Process results
365
  result_idx = 0
366
  current_col_group = 0
 
370
  if current_row_pair >= len(row_pairs):
371
  break
372
 
 
373
  cols = column_groups[current_col_group]
374
  rows = row_pairs[current_row_pair]
375
 
376
+ # Write data and add formulas
377
  if result_idx < len(self.results):
378
  result = self.results[result_idx]
379
+ self._write_result_to_cells(ws, result, cols, rows[0])
 
 
 
 
 
 
 
 
 
380
  result_idx += 1
381
 
 
382
  if result_idx < len(self.results):
383
  result = self.results[result_idx]
384
+ self._write_result_to_cells(ws, result, cols, rows[1])
 
 
 
 
 
 
 
 
 
385
  result_idx += 1
386
 
387
+ # Add formulas for this group
388
+ self.add_formulas_to_template(ws, rows, cols)
389
 
390
+ current_col_group += 1
391
  if current_col_group >= len(column_groups):
392
  current_col_group = 0
393
  current_row_pair += 1
394
 
 
 
 
395
  wb.save(output_path)
396
+ return output_path, f"Results saved successfully ({result_idx} measurements)"
 
397
 
398
  except Exception as e:
399
+ logger.error(f"Error saving formatted results: {str(e)}")
400
+ return None, f"Error saving results: {str(e)}"
401
+
402
+ def _write_result_to_cells(self, ws, result, cols, row):
403
+ """Helper method to write a single result to worksheet cells"""
404
+ ws[f"{cols[0]}{row}"] = float(result['Area (mm²)'])
405
+ ws[f"{cols[1]}{row}"] = float(result['Mean'])
406
+ ws[f"{cols[2]}{row}"] = float(result['StdDev'])
407
+ ws[f"{cols[3]}{row}"] = float(result['Min'])
408
+ ws[f"{cols[4]}{row}"] = float(result['Max'])
409
+
410
+ def format_results(self):
411
+ if not self.results:
412
+ return "No measurements yet"
413
+ df = pd.DataFrame(self.results)
414
+ columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
415
+ df = df[columns_order]
416
+ return df.to_string(index=False)
417
 
418
  def add_blank_row(self, image):
419
  self.results.append({
 
453
  with gr.Row():
454
  with gr.Column():
455
  file_input = gr.File(label="Upload DICOM file")
 
456
  diameter_slider = gr.Slider(
457
  minimum=1,
458
  maximum=20,
 
465
  zoom_in_btn = gr.Button("Zoom In (+)")
466
  zoom_out_btn = gr.Button("Zoom Out (-)")
467
  reset_btn = gr.Button("Reset View")
468
+ reset_all_btn = gr.Button("Reset All") # New reset all button
469
 
470
  with gr.Column():
471
  image_display = gr.Image(
 
479
  zero_btn = gr.Button("Add Zero Row")
480
  undo_btn = gr.Button("Undo Last")
481
  save_btn = gr.Button("Save Results")
482
+ save_formatted_btn = gr.Button("Save Formatted Results") # Renamed button
483
 
484
  results_display = gr.Textbox(label="Results", interactive=False)
485
  file_output = gr.File(label="Download Results")
 
490
  - Use arrow keys to pan when zoomed in
491
  - Click points to measure
492
  - Use Zoom In/Out buttons or Reset View to adjust zoom level
493
+ - Use Reset All to clear all measurements
494
+ - Save Formatted Results will create Excel file with formulas
495
  """)
496
 
497
  def update_diameter(x):
 
499
  print(f"Diameter updated to: {x}")
500
  return f"Diameter set to {x} pixels"
501
 
502
+ def save_formatted():
503
+ output_path = "analysis_results_formatted.xlsx"
504
+ return analyzer.save_formatted_results(output_path)
 
 
 
505
 
506
  # Event handlers
507
  file_input.change(
 
540
  outputs=image_display
541
  )
542
 
543
+ # New reset all button handler
544
+ reset_all_btn.click(
545
+ fn=analyzer.reset_all,
546
+ inputs=image_display,
547
+ outputs=[image_display, results_display]
548
+ )
549
+
550
  key_press.change(
551
  fn=analyzer.handle_keyboard,
552
  inputs=key_press,
 
576
  outputs=[file_output, results_display]
577
  )
578
 
579
+ save_formatted_btn.click(
580
+ fn=save_formatted,
 
581
  outputs=[file_output, results_display]
582
  )
583