HeshamAI commited on
Commit
ad031b6
·
verified ·
1 Parent(s): 95bfe52

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -179
app.py CHANGED
@@ -3,16 +3,13 @@ import cv2
3
  import numpy as np
4
  import pandas as pd
5
  import pydicom
6
- import io
7
- import os
8
- from PIL import Image
9
  import tempfile
 
10
 
11
  class DicomAnalyzer:
12
  def __init__(self):
13
  self.results = []
14
  self.circle_diameter = 9
15
- self.zoom_factor = 1.0
16
  self.current_image1 = None
17
  self.current_image2 = None
18
  self.dicom_data1 = None
@@ -24,47 +21,37 @@ class DicomAnalyzer:
24
 
25
  def load_dicom(self, file):
26
  try:
27
- if file is None:
28
- return None, None, None
29
-
30
  dicom_data = pydicom.dcmread(file.name)
31
  image = dicom_data.pixel_array.astype(np.float32)
32
-
33
  # Apply rescale slope and intercept
34
  rescale_slope = getattr(dicom_data, 'RescaleSlope', 1)
35
  rescale_intercept = getattr(dicom_data, 'RescaleIntercept', 0)
36
  image = (image * rescale_slope) + rescale_intercept
37
-
38
- # Store original image for analysis
39
- original_image = image.copy()
40
-
41
  # Normalize for display
42
  image_display = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
43
-
44
- # Convert to BGR for visualization
45
- if len(image_display.shape) == 2:
46
- image_display = cv2.cvtColor(image_display, cv2.COLOR_GRAY2BGR)
47
-
48
- return original_image, image_display, dicom_data
49
  except Exception as e:
50
  print(f"Error loading DICOM file: {str(e)}")
51
  return None, None, None
52
 
53
  def analyze_point(self, image, dicom_data, x, y):
54
  try:
55
- # Create a circular mask
56
  mask = np.zeros_like(image, dtype=np.uint8)
57
  y_indices, x_indices = np.ogrid[:image.shape[0], :image.shape[1]]
58
- distance_from_center = np.sqrt((x_indices - x)**2 + (y_indices - y)**2)
59
  mask[distance_from_center <= self.circle_diameter / 2] = 1
60
 
61
- # Extract pixel values within the circle
62
  pixels = image[mask == 1]
63
 
64
  # Calculate metrics
65
  area_pixels = np.sum(mask)
66
  pixel_spacing = float(dicom_data.PixelSpacing[0])
67
- area_mm2 = area_pixels * (pixel_spacing**2)
68
  mean = np.mean(pixels)
69
  stddev = np.std(pixels)
70
  min_val = np.min(pixels)
@@ -81,87 +68,75 @@ class DicomAnalyzer:
81
  print(f"Error analyzing point: {str(e)}")
82
  return None
83
 
84
- def draw_circle(self, image, x, y, is_image1=True):
85
- try:
86
- image_copy = image.copy()
87
-
88
- # Draw all previous marks
89
- marks = self.marks1 if is_image1 else self.marks2
90
- for mark_x, mark_y in marks:
91
- cv2.circle(image_copy,
92
- (int(mark_x), int(mark_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(mark_x), int(mark_y)),
98
- int(self.circle_diameter / 2) - 2,
99
- (255, 255, 255), 1, # White inner ring
100
- lineType=cv2.LINE_AA)
101
-
102
- # Draw the new mark
103
- cv2.circle(image_copy,
104
- (int(x), int(y)),
105
- int(self.circle_diameter / 2),
106
- (0, 255, 255), 2, # Yellow outer ring
107
- lineType=cv2.LINE_AA)
108
- cv2.circle(image_copy,
109
- (int(x), int(y)),
110
- int(self.circle_diameter / 2) - 2,
111
- (255, 255, 255), 1, # White inner ring
112
- lineType=cv2.LINE_AA)
113
-
114
- # Store the new mark
115
- if is_image1:
116
- self.marks1.append((x, y))
117
- else:
118
- self.marks2.append((x, y))
119
-
120
- return image_copy
121
- except Exception as e:
122
- print(f"Error drawing circle: {str(e)}")
123
- return image
124
-
125
-
126
- def handle_click1(self, evt: gr.SelectData):
127
- if self.current_image1 is None:
128
- return self.image_display1, "Please load Image 1 first"
129
-
130
  try:
131
- x, y = evt.index
132
- marked_image = self.draw_circle(self.image_display1, x, y, is_image1=True)
133
- self.image_display1 = marked_image
134
-
135
- results = self.analyze_point(self.current_image1, self.dicom_data1, x, y)
136
- if results:
137
- results['Image'] = "Image 1"
138
- results['Point'] = f"({x}, {y})"
139
- self.results.append(results)
140
-
141
- return self.image_display1, self.format_results()
 
 
 
 
 
 
 
 
 
 
 
 
142
  except Exception as e:
143
- print(f"Error in handle_click1: {str(e)}")
144
- return self.image_display1, f"Error: {str(e)}"
145
-
146
- def handle_click2(self, evt: gr.SelectData):
147
- if self.current_image2 is None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  return self.image_display2, "Please load Image 2 first"
149
-
150
  try:
151
  x, y = evt.index
152
- marked_image = self.draw_circle(self.image_display2, x, y, is_image1=False)
153
- self.image_display2 = marked_image
154
-
155
- results = self.analyze_point(self.current_image2, self.dicom_data2, x, y)
 
 
 
 
 
156
  if results:
157
- results['Image'] = "Image 2"
158
  results['Point'] = f"({x}, {y})"
159
  self.results.append(results)
160
-
161
- return self.image_display2, self.format_results()
162
  except Exception as e:
163
- print(f"Error in handle_click2: {str(e)}")
164
- return self.image_display2, f"Error: {str(e)}"
165
 
166
  def format_results(self):
167
  if not self.results:
@@ -185,36 +160,14 @@ def draw_circle(self, image, x, y, is_image1=True):
185
  )
186
  return "Results cleared", self.image_display1, self.image_display2
187
 
188
- def add_blank_row(self):
189
- self.results.append({
190
- 'Image': '',
191
- 'Point': '',
192
- 'Area (mm²)': '',
193
- 'Mean': '',
194
- 'StdDev': '',
195
- 'Min': '',
196
- 'Max': ''
197
- })
198
- return self.format_results()
199
-
200
- def update_circle_diameter(self, value):
201
- self.circle_diameter = value
202
- return f"Circle diameter set to {value}"
203
-
204
  def save_results(self):
205
  try:
206
  if not self.results:
207
  return None, "No results to save"
208
-
209
  df = pd.DataFrame(self.results)
210
-
211
- # Create temporary file
212
- temp_dir = tempfile.gettempdir()
213
- temp_file = os.path.join(temp_dir, "analysis_results.xlsx")
214
-
215
- # Save to Excel
216
  df.to_excel(temp_file, index=False, engine='openpyxl')
217
-
218
  return temp_file, "Results saved successfully. Click to download."
219
  except Exception as e:
220
  print(f"Error saving results: {str(e)}")
@@ -222,73 +175,33 @@ def draw_circle(self, image, x, y, is_image1=True):
222
 
223
  def create_interface():
224
  analyzer = DicomAnalyzer()
225
-
226
  with gr.Blocks() as interface:
227
  gr.Markdown("# CT DICOM Image Analyzer")
228
-
229
- with gr.Row():
230
- with gr.Column():
231
- file1 = gr.File(label="Upload first DICOM file")
232
- image1 = gr.Image(label="Image 1", interactive=True, type="numpy")
233
- file1.change(fn=analyzer.process_image1, inputs=file1, outputs=image1)
234
-
235
- with gr.Column():
236
- file2 = gr.File(label="Upload second DICOM file")
237
- image2 = gr.Image(label="Image 2", interactive=True, type="numpy")
238
- file2.change(fn=analyzer.process_image2, inputs=file2, outputs=image2)
239
-
240
- with gr.Row():
241
- circle_diameter = gr.Slider(
242
- minimum=1,
243
- maximum=20,
244
- value=9,
245
- step=1,
246
- label="Circle Diameter"
247
- )
248
-
249
  with gr.Row():
250
- clear_btn = gr.Button("Clear Results")
251
- blank_row_btn = gr.Button("Add Blank Row")
252
- save_btn = gr.Button("Save Results")
253
-
 
 
 
 
 
 
 
254
  results = gr.Textbox(label="Results", interactive=False)
255
- file_output = gr.File(label="Download Results")
256
- status = gr.Textbox(label="Status")
257
-
258
- # Connect events
259
- circle_diameter.change(
260
- fn=analyzer.update_circle_diameter,
261
- inputs=circle_diameter,
262
- outputs=status
263
- )
264
-
265
- image1.select(
266
- fn=analyzer.handle_click1,
267
- outputs=[image1, results]
268
- )
269
-
270
- image2.select(
271
- fn=analyzer.handle_click2,
272
- outputs=[image2, results]
273
- )
274
-
275
- clear_btn.click(
276
- fn=analyzer.clear_results,
277
- outputs=[status, image1, image2]
278
- )
279
-
280
- blank_row_btn.click(
281
- fn=analyzer.add_blank_row,
282
- outputs=results
283
- )
284
-
285
- save_btn.click(
286
- fn=analyzer.save_results,
287
- outputs=[file_output, status]
288
- )
289
 
290
  return interface
291
 
292
  if __name__ == "__main__":
293
  interface = create_interface()
294
- interface.launch()
 
3
  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
 
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
+
27
  # Apply rescale slope and intercept
28
  rescale_slope = getattr(dicom_data, 'RescaleSlope', 1)
29
  rescale_intercept = getattr(dicom_data, 'RescaleIntercept', 0)
30
  image = (image * rescale_slope) + rescale_intercept
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:
38
  print(f"Error loading DICOM file: {str(e)}")
39
  return None, None, None
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
  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 process_image1(self, file):
101
+ image, image_display, dicom_data = self.load_dicom(file)
102
+ self.current_image1 = image
103
+ self.image_display1 = image_display
104
+ self.dicom_data1 = dicom_data
105
+ return image_display
106
+
107
+ def process_image2(self, file):
108
+ image, image_display, dicom_data = self.load_dicom(file)
109
+ self.current_image2 = image
110
+ self.image_display2 = image_display
111
+ self.dicom_data2 = dicom_data
112
+ return image_display
113
+
114
+ def handle_click(self, evt: gr.SelectData, is_image1=True):
115
+ if is_image1 and self.current_image1 is None:
116
+ return self.image_display1, "Please load Image 1 first"
117
+ elif not is_image1 and self.current_image2 is None:
118
  return self.image_display2, "Please load Image 2 first"
119
+
120
  try:
121
  x, y = evt.index
122
+ if is_image1:
123
+ marked_image = self.draw_circle(self.image_display1, x, y, is_image1=True)
124
+ self.image_display1 = marked_image
125
+ results = self.analyze_point(self.current_image1, self.dicom_data1, x, y)
126
+ else:
127
+ marked_image = self.draw_circle(self.image_display2, x, y, is_image1=False)
128
+ self.image_display2 = marked_image
129
+ results = self.analyze_point(self.current_image2, self.dicom_data2, x, y)
130
+
131
  if results:
132
+ results['Image'] = "Image 1" if is_image1 else "Image 2"
133
  results['Point'] = f"({x}, {y})"
134
  self.results.append(results)
135
+
136
+ return marked_image, self.format_results()
137
  except Exception as e:
138
+ print(f"Error in handle_click: {str(e)}")
139
+ return (self.image_display1 if is_image1 else self.image_display2), f"Error: {str(e)}"
140
 
141
  def format_results(self):
142
  if not self.results:
 
160
  )
161
  return "Results cleared", self.image_display1, self.image_display2
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  def save_results(self):
164
  try:
165
  if not self.results:
166
  return None, "No results to save"
167
+
168
  df = pd.DataFrame(self.results)
169
+ temp_file = os.path.join(tempfile.gettempdir(), "analysis_results.xlsx")
 
 
 
 
 
170
  df.to_excel(temp_file, index=False, engine='openpyxl')
 
171
  return temp_file, "Results saved successfully. Click to download."
172
  except Exception as e:
173
  print(f"Error saving results: {str(e)}")
 
175
 
176
  def create_interface():
177
  analyzer = DicomAnalyzer()
178
+
179
  with gr.Blocks() as interface:
180
  gr.Markdown("# CT DICOM Image Analyzer")
181
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  with gr.Row():
183
+ file1 = gr.File(label="Upload first DICOM file")
184
+ image1 = gr.Image(label="Image 1", interactive=True, type="numpy")
185
+ file1.change(fn=analyzer.process_image1, inputs=file1, outputs=image1)
186
+
187
+ file2 = gr.File(label="Upload second DICOM file")
188
+ image2 = gr.Image(label="Image 2", interactive=True, type="numpy")
189
+ file2.change(fn=analyzer.process_image2, inputs=file2, outputs=image2)
190
+
191
+ circle_diameter = gr.Slider(minimum=1, maximum=20, value=9, step=1, label="Circle Diameter")
192
+ circle_diameter.change(fn=analyzer.update_circle_diameter, inputs=circle_diameter, outputs=None)
193
+
194
  results = gr.Textbox(label="Results", interactive=False)
195
+ clear_btn = gr.Button("Clear Results")
196
+ save_btn = gr.Button("Save Results")
197
+
198
+ image1.select(fn=lambda evt: analyzer.handle_click(evt, is_image1=True), outputs=[image1, results])
199
+ image2.select(fn=lambda evt: analyzer.handle_click(evt, is_image1=False), outputs=[image2, results])
200
+ clear_btn.click(fn=analyzer.clear_results, outputs=[results, image1, image2])
201
+ save_btn.click(fn=analyzer.save_results, outputs=["file", results])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
  return interface
204
 
205
  if __name__ == "__main__":
206
  interface = create_interface()
207
+ interface.launch()