HeshamAI commited on
Commit
1a59731
·
verified ·
1 Parent(s): 30cbfa0

Update app.py

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