HeshamAI commited on
Commit
d9edee5
·
verified ·
1 Parent(s): 200d335

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -37
app.py CHANGED
@@ -4,23 +4,21 @@ import numpy as np
4
  import pandas as pd
5
  import pydicom
6
  import tempfile
7
- import os
8
 
9
  class DicomAnalyzer:
10
  def __init__(self):
11
  self.results = []
12
  self.circle_diameter = 9
13
- self.current_image1 = None
14
- self.current_image2 = None
15
- self.dicom_data1 = None
16
- self.dicom_data2 = None
17
- self.image_display1 = None
18
- self.image_display2 = None
19
- self.marks1 = []
20
- self.marks2 = []
21
 
22
  def load_dicom(self, file):
23
  try:
 
 
 
24
  dicom_data = pydicom.dcmread(file.name)
25
  image = dicom_data.pixel_array.astype(np.float32)
26
 
@@ -31,7 +29,10 @@ class DicomAnalyzer:
31
 
32
  # Normalize for display
33
  image_display = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
34
- image_display = cv2.cvtColor(image_display, cv2.COLOR_GRAY2BGR)
 
 
 
35
 
36
  return image, image_display, dicom_data
37
  except Exception as e:
@@ -40,18 +41,19 @@ class DicomAnalyzer:
40
 
41
  def analyze_point(self, image, dicom_data, x, y):
42
  try:
 
43
  mask = np.zeros_like(image, dtype=np.uint8)
44
  y_indices, x_indices = np.ogrid[:image.shape[0], :image.shape[1]]
45
- distance_from_center = np.sqrt((x_indices - x) ** 2 + (y_indices - y) ** 2)
46
  mask[distance_from_center <= self.circle_diameter / 2] = 1
47
 
48
- # Extract pixel values
49
  pixels = image[mask == 1]
50
 
51
  # Calculate metrics
52
  area_pixels = np.sum(mask)
53
  pixel_spacing = float(dicom_data.PixelSpacing[0])
54
- area_mm2 = area_pixels * (pixel_spacing ** 2)
55
  mean = np.mean(pixels)
56
  stddev = np.std(pixels)
57
  min_val = np.min(pixels)
@@ -68,39 +70,108 @@ class DicomAnalyzer:
68
  print(f"Error analyzing point: {str(e)}")
69
  return None
70
 
71
- def draw_circle(self, image, x, y, is_image1=True):
72
  try:
73
  image_copy = image.copy()
74
 
75
  # Draw all previous marks
76
- marks = self.marks1 if is_image1 else self.marks2
77
- for mark_x, mark_y in marks:
78
- cv2.circle(image_copy, (int(mark_x), int(mark_y)), int(self.circle_diameter / 2),
79
- (0, 255, 255), 2, lineType=cv2.LINE_AA) # Yellow outer ring
80
- cv2.circle(image_copy, (int(mark_x), int(mark_y)), int(self.circle_diameter / 2) - 2,
81
- (255, 255, 255), 1, lineType=cv2.LINE_AA) # White inner ring
82
-
83
- # Draw new mark
84
- cv2.circle(image_copy, (int(x), int(y)), int(self.circle_diameter / 2),
85
- (0, 255, 255), 2, lineType=cv2.LINE_AA) # Yellow outer ring
86
- cv2.circle(image_copy, (int(x), int(y)), int(self.circle_diameter / 2) - 2,
87
- (255, 255, 255), 1, lineType=cv2.LINE_AA) # White inner ring
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  # Store the new mark
90
- if is_image1:
91
- self.marks1.append((x, y))
92
- else:
93
- self.marks2.append((x, y))
94
-
95
  return image_copy
96
  except Exception as e:
97
  print(f"Error drawing circle: {str(e)}")
98
  return image
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  def update_circle_diameter(self, value):
101
  self.circle_diameter = value
102
  return f"Circle diameter set to {value}"
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  def create_interface():
105
  analyzer = DicomAnalyzer()
106
 
@@ -108,14 +179,48 @@ def create_interface():
108
  gr.Markdown("# CT DICOM Image Analyzer")
109
 
110
  with gr.Row():
111
- file1 = gr.File(label="Upload DICOM file")
112
- image1 = gr.Image(label="DICOM Image", interactive=True, type="numpy")
113
- file1.change(fn=analyzer.load_dicom, inputs=file1, outputs=image1)
114
 
115
- circle_diameter = gr.Slider(minimum=1, maximum=20, value=9, step=1, label="Circle Diameter")
116
- status = gr.Textbox(label="Status", interactive=False)
 
 
 
 
 
 
117
 
118
- circle_diameter.change(fn=analyzer.update_circle_diameter, inputs=circle_diameter, outputs=status)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  return interface
121
 
 
4
  import pandas as pd
5
  import pydicom
6
  import tempfile
 
7
 
8
  class DicomAnalyzer:
9
  def __init__(self):
10
  self.results = []
11
  self.circle_diameter = 9
12
+ self.current_image = None
13
+ self.dicom_data = None
14
+ self.image_display = None
15
+ self.marks = []
 
 
 
 
16
 
17
  def load_dicom(self, file):
18
  try:
19
+ if file is None:
20
+ return None, None, None
21
+
22
  dicom_data = pydicom.dcmread(file.name)
23
  image = dicom_data.pixel_array.astype(np.float32)
24
 
 
29
 
30
  # Normalize for display
31
  image_display = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
32
+
33
+ # Convert to BGR for visualization
34
+ if len(image_display.shape) == 2:
35
+ image_display = cv2.cvtColor(image_display, cv2.COLOR_GRAY2BGR)
36
 
37
  return image, image_display, dicom_data
38
  except Exception as e:
 
41
 
42
  def analyze_point(self, image, dicom_data, x, y):
43
  try:
44
+ # Create a circular mask
45
  mask = np.zeros_like(image, dtype=np.uint8)
46
  y_indices, x_indices = np.ogrid[:image.shape[0], :image.shape[1]]
47
+ distance_from_center = np.sqrt((x_indices - x)**2 + (y_indices - y)**2)
48
  mask[distance_from_center <= self.circle_diameter / 2] = 1
49
 
50
+ # Extract pixel values within the circle
51
  pixels = image[mask == 1]
52
 
53
  # Calculate metrics
54
  area_pixels = np.sum(mask)
55
  pixel_spacing = float(dicom_data.PixelSpacing[0])
56
+ area_mm2 = area_pixels * (pixel_spacing**2)
57
  mean = np.mean(pixels)
58
  stddev = np.std(pixels)
59
  min_val = np.min(pixels)
 
70
  print(f"Error analyzing point: {str(e)}")
71
  return None
72
 
73
+ def draw_circle(self, image, x, y):
74
  try:
75
  image_copy = image.copy()
76
 
77
  # Draw all previous marks
78
+ for mark_x, mark_y in self.marks:
79
+ cv2.circle(image_copy,
80
+ (int(mark_x), int(mark_y)),
81
+ int(self.circle_diameter / 2),
82
+ (0, 255, 255), 2, # Yellow outer ring
83
+ lineType=cv2.LINE_AA)
84
+ cv2.circle(image_copy,
85
+ (int(mark_x), int(mark_y)),
86
+ int(self.circle_diameter / 2) - 2,
87
+ (255, 255, 255), 1, # White inner ring
88
+ lineType=cv2.LINE_AA)
89
+
90
+ # Draw the new mark
91
+ cv2.circle(image_copy,
92
+ (int(x), int(y)),
93
+ int(self.circle_diameter / 2),
94
+ (0, 255, 255), 2, # Yellow outer ring
95
+ lineType=cv2.LINE_AA)
96
+ cv2.circle(image_copy,
97
+ (int(x), int(y)),
98
+ int(self.circle_diameter / 2) - 2,
99
+ (255, 255, 255), 1, # White inner ring
100
+ lineType=cv2.LINE_AA)
101
 
102
  # Store the new mark
103
+ self.marks.append((x, y))
104
+
 
 
 
105
  return image_copy
106
  except Exception as e:
107
  print(f"Error drawing circle: {str(e)}")
108
  return image
109
 
110
+ def process_image(self, file):
111
+ image, image_display, dicom_data = self.load_dicom(file)
112
+ self.current_image = image
113
+ self.image_display = image_display
114
+ self.dicom_data = dicom_data
115
+ return image_display
116
+
117
+ def handle_click(self, evt: gr.SelectData):
118
+ if self.current_image is None:
119
+ return self.image_display, "Please load the DICOM file first"
120
+
121
+ try:
122
+ x, y = evt.index
123
+ marked_image = self.draw_circle(self.image_display, x, y)
124
+ self.image_display = marked_image
125
+
126
+ results = self.analyze_point(self.current_image, self.dicom_data, x, y)
127
+ if results:
128
+ results['Point'] = f"({x}, {y})"
129
+ self.results.append(results)
130
+
131
+ return self.image_display, self.format_results()
132
+ except Exception as e:
133
+ print(f"Error in handle_click: {str(e)}")
134
+ return self.image_display, f"Error: {str(e)}"
135
+
136
+ def format_results(self):
137
+ if not self.results:
138
+ return "No results yet"
139
+ df = pd.DataFrame(self.results)
140
+ return df.to_string()
141
+
142
+ def clear_results(self):
143
+ self.results = []
144
+ self.marks = []
145
+ if self.current_image is not None:
146
+ self.image_display = cv2.cvtColor(
147
+ cv2.normalize(self.current_image, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8),
148
+ cv2.COLOR_GRAY2BGR
149
+ )
150
+ return "Results cleared", self.image_display
151
+
152
  def update_circle_diameter(self, value):
153
  self.circle_diameter = value
154
  return f"Circle diameter set to {value}"
155
 
156
+ def save_results(self):
157
+ try:
158
+ if not self.results:
159
+ return None, "No results to save"
160
+
161
+ df = pd.DataFrame(self.results)
162
+
163
+ # Create temporary file
164
+ temp_dir = tempfile.gettempdir()
165
+ temp_file = os.path.join(temp_dir, "analysis_results.xlsx")
166
+
167
+ # Save to Excel
168
+ df.to_excel(temp_file, index=False, engine='openpyxl')
169
+
170
+ return temp_file, "Results saved successfully. Click to download."
171
+ except Exception as e:
172
+ print(f"Error saving results: {str(e)}")
173
+ return None, f"Error saving results: {str(e)}"
174
+
175
  def create_interface():
176
  analyzer = DicomAnalyzer()
177
 
 
179
  gr.Markdown("# CT DICOM Image Analyzer")
180
 
181
  with gr.Row():
182
+ file = gr.File(label="Upload DICOM File")
183
+ image = gr.Image(label="DICOM Image", interactive=True, type="numpy")
184
+ file.change(fn=analyzer.process_image, inputs=file, outputs=image)
185
 
186
+ with gr.Row():
187
+ circle_diameter = gr.Slider(
188
+ minimum=1,
189
+ maximum=20,
190
+ value=9,
191
+ step=1,
192
+ label="Circle Diameter"
193
+ )
194
 
195
+ with gr.Row():
196
+ clear_btn = gr.Button("Clear Results")
197
+ save_btn = gr.Button("Save Results")
198
+
199
+ results = gr.Textbox(label="Results", interactive=False)
200
+ file_output = gr.File(label="Download Results")
201
+ status = gr.Textbox(label="Status")
202
+
203
+ # Connect events
204
+ circle_diameter.change(
205
+ fn=analyzer.update_circle_diameter,
206
+ inputs=circle_diameter,
207
+ outputs=status
208
+ )
209
+
210
+ image.select(
211
+ fn=analyzer.handle_click,
212
+ outputs=[image, results]
213
+ )
214
+
215
+ clear_btn.click(
216
+ fn=analyzer.clear_results,
217
+ outputs=[status, image]
218
+ )
219
+
220
+ save_btn.click(
221
+ fn=analyzer.save_results,
222
+ outputs=[file_output, status]
223
+ )
224
 
225
  return interface
226