HeshamAI commited on
Commit
84bc2f7
·
verified ·
1 Parent(s): 4b6af2c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -247
app.py CHANGED
@@ -60,7 +60,15 @@ class DicomAnalyzer:
60
 
61
  def normalize_image(self, image):
62
  try:
63
- normalized = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
 
 
 
 
 
 
 
 
64
  if len(normalized.shape) == 2:
65
  normalized = cv2.cvtColor(normalized, cv2.COLOR_GRAY2RGB)
66
  return normalized
@@ -100,21 +108,34 @@ class DicomAnalyzer:
100
  zoomed = cv2.resize(self.original_display, (new_width, new_height),
101
  interpolation=cv2.INTER_CUBIC)
102
 
103
- # Draw marks on the zoomed image
104
  for x, y, diameter in self.marks:
105
- # Calculate zoomed coordinates
106
  zoomed_x = int(x * self.zoom_factor)
107
  zoomed_y = int(y * self.zoom_factor)
108
  zoomed_diameter = int(diameter * self.zoom_factor)
109
 
 
110
  cv2.circle(zoomed,
111
  (zoomed_x, zoomed_y),
112
  zoomed_diameter // 2,
113
- (255, 255, 0), # BGR: Yellow
114
- 2,
115
  lineType=cv2.LINE_AA)
116
-
117
- # Extract visible portion considering pan
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  visible_height = min(height, new_height)
119
  visible_width = min(width, new_width)
120
 
@@ -149,243 +170,4 @@ class DicomAnalyzer:
149
  return self.update_display()
150
  except Exception as e:
151
  print(f"Error handling keyboard input: {str(e)}")
152
- return self.display_image
153
-
154
- def analyze_roi(self, evt: gr.SelectData):
155
- try:
156
- if self.current_image is None:
157
- return None, "No image loaded"
158
-
159
- x = int((evt.index[0] + self.pan_x) / self.zoom_factor)
160
- y = int((evt.index[1] + self.pan_y) / self.zoom_factor)
161
-
162
- mask = np.zeros_like(self.current_image, dtype=np.uint8)
163
- y_indices, x_indices = np.ogrid[:self.current_image.shape[0], :self.current_image.shape[1]]
164
- distance_from_center = np.sqrt((x_indices - x) ** 2 + (y_indices - y) ** 2)
165
- mask[distance_from_center <= self.circle_diameter / 2] = 1
166
-
167
- roi_pixels = self.current_image[mask == 1]
168
-
169
- pixel_spacing = float(self.dicom_data.PixelSpacing[0])
170
- area_pixels = np.sum(mask)
171
- area_mm2 = area_pixels * (pixel_spacing ** 2)
172
- mean = np.mean(roi_pixels)
173
- stddev = np.std(roi_pixels)
174
- min_val = np.min(roi_pixels)
175
- max_val = np.max(roi_pixels)
176
-
177
- result = {
178
- 'Area (mm²)': f"{area_mm2:.3f}",
179
- 'Mean': f"{mean:.3f}",
180
- 'StdDev': f"{stddev:.3f}",
181
- 'Min': f"{min_val:.3f}",
182
- 'Max': f"{max_val:.3f}",
183
- 'Point': f"({x}, {y})"
184
- }
185
- self.results.append(result)
186
- self.marks.append((x, y, self.circle_diameter))
187
- print(f"ROI analyzed at point ({x}, {y})")
188
-
189
- return self.update_display(), self.format_results()
190
- except Exception as e:
191
- print(f"Error analyzing ROI: {str(e)}")
192
- return self.display_image, f"Error analyzing ROI: {str(e)}"
193
-
194
- def format_results(self):
195
- if not self.results:
196
- return "No measurements yet"
197
- df = pd.DataFrame(self.results)
198
- columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
199
- df = df[columns_order]
200
- return df.to_string(index=False)
201
-
202
- def add_blank_row(self, image):
203
- self.results.append({
204
- 'Area (mm²)': '',
205
- 'Mean': '',
206
- 'StdDev': '',
207
- 'Min': '',
208
- 'Max': '',
209
- 'Point': ''
210
- })
211
- return image, self.format_results()
212
-
213
- def add_zero_row(self, image):
214
- self.results.append({
215
- 'Area (mm²)': '0.000',
216
- 'Mean': '0.000',
217
- 'StdDev': '0.000',
218
- 'Min': '0.000',
219
- 'Max': '0.000',
220
- 'Point': '(0, 0)'
221
- })
222
- return image, self.format_results()
223
-
224
- def undo_last(self, image):
225
- if self.results:
226
- self.results.pop()
227
- if self.marks:
228
- self.marks.pop()
229
- return self.update_display(), self.format_results()
230
-
231
- def save_results(self):
232
- try:
233
- if not self.results:
234
- return None, "No results to save"
235
-
236
- df = pd.DataFrame(self.results)
237
- columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
238
- df = df[columns_order]
239
-
240
- temp_file = "analysis_results.xlsx"
241
- df.to_excel(temp_file, index=False)
242
-
243
- return temp_file, "Results saved successfully"
244
- except Exception as e:
245
- return None, f"Error saving results: {str(e)}"
246
-
247
- def create_interface():
248
- print("Creating interface...")
249
- analyzer = DicomAnalyzer()
250
-
251
- with gr.Blocks(css="#image_display { outline: none; }") as interface:
252
- gr.Markdown("# DICOM Image Analyzer")
253
-
254
- with gr.Row():
255
- with gr.Column():
256
- file_input = gr.File(label="Upload DICOM file")
257
- diameter_slider = gr.Slider(
258
- minimum=1,
259
- maximum=20,
260
- value=9,
261
- step=1,
262
- label="ROI Diameter (pixels)"
263
- )
264
-
265
- with gr.Row():
266
- zoom_in_btn = gr.Button("Zoom In (+)")
267
- zoom_out_btn = gr.Button("Zoom Out (-)")
268
- reset_btn = gr.Button("Reset View")
269
-
270
- with gr.Column():
271
- image_display = gr.Image(label="DICOM Image", interactive=True, elem_id="image_display")
272
-
273
- with gr.Row():
274
- blank_btn = gr.Button("Add Blank Row")
275
- zero_btn = gr.Button("Add Zero Row")
276
- undo_btn = gr.Button("Undo Last")
277
- save_btn = gr.Button("Save Results")
278
-
279
- results_display = gr.Textbox(label="Results", interactive=False)
280
- file_output = gr.File(label="Download Results")
281
- key_press = gr.Textbox(visible=False, elem_id="key_press")
282
-
283
- gr.Markdown("""
284
- ### Controls:
285
- - Use arrow keys to pan when zoomed in
286
- - Click points to measure
287
- - Use Zoom In/Out buttons or Reset View to adjust zoom level
288
- """)
289
-
290
- def update_diameter(x):
291
- analyzer.circle_diameter = x
292
- print(f"Diameter updated to: {x}")
293
- return f"Diameter set to {x} pixels"
294
-
295
- # Event handlers
296
- file_input.change(
297
- fn=analyzer.load_dicom,
298
- inputs=file_input,
299
- outputs=[image_display, results_display]
300
- )
301
-
302
- image_display.select(
303
- fn=analyzer.analyze_roi,
304
- outputs=[image_display, results_display]
305
- )
306
-
307
- diameter_slider.change(
308
- fn=update_diameter,
309
- inputs=diameter_slider,
310
- outputs=gr.Textbox(label="Status")
311
- )
312
-
313
- zoom_in_btn.click(
314
- fn=analyzer.zoom_in,
315
- inputs=image_display,
316
- outputs=image_display
317
- )
318
-
319
- zoom_out_btn.click(
320
- fn=analyzer.zoom_out,
321
- inputs=image_display,
322
- outputs=image_display
323
- )
324
-
325
- reset_btn.click(
326
- fn=analyzer.reset_view,
327
- outputs=image_display
328
- )
329
-
330
- key_press.change(
331
- fn=analyzer.handle_keyboard,
332
- inputs=key_press,
333
- outputs=image_display
334
- )
335
-
336
- blank_btn.click(
337
- fn=analyzer.add_blank_row,
338
- inputs=image_display,
339
- outputs=[image_display, results_display]
340
- )
341
-
342
- zero_btn.click(
343
- fn=analyzer.add_zero_row,
344
- inputs=image_display,
345
- outputs=[image_display, results_display]
346
- )
347
-
348
- undo_btn.click(
349
- fn=analyzer.undo_last,
350
- inputs=image_display,
351
- outputs=[image_display, results_display]
352
- )
353
-
354
- save_btn.click(
355
- fn=analyzer.save_results,
356
- outputs=[file_output, results_display]
357
- )
358
-
359
- js = """
360
- <script>
361
- document.addEventListener('keydown', function(e) {
362
- if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
363
- e.preventDefault();
364
- const keyPressElement = document.querySelector('#key_press textarea');
365
- if (keyPressElement) {
366
- keyPressElement.value = e.key;
367
- keyPressElement.dispatchEvent(new Event('input'));
368
- }
369
- }
370
- });
371
- </script>
372
- """
373
- gr.HTML(js)
374
-
375
- print("Interface created successfully")
376
- return interface
377
-
378
- if __name__ == "__main__":
379
- try:
380
- print("Starting application...")
381
- interface = create_interface()
382
- print("Launching interface...")
383
- interface.launch(
384
- server_name="0.0.0.0",
385
- server_port=7860,
386
- share=True,
387
- debug=True
388
- )
389
- except Exception as e:
390
- print(f"Error launching application: {str(e)}")
391
- raise e
 
60
 
61
  def normalize_image(self, image):
62
  try:
63
+ # Improve image normalization
64
+ normalized = cv2.normalize(
65
+ image,
66
+ None,
67
+ alpha=0,
68
+ beta=255,
69
+ norm_type=cv2.NORM_MINMAX,
70
+ dtype=cv2.CV_8U
71
+ )
72
  if len(normalized.shape) == 2:
73
  normalized = cv2.cvtColor(normalized, cv2.COLOR_GRAY2RGB)
74
  return normalized
 
108
  zoomed = cv2.resize(self.original_display, (new_width, new_height),
109
  interpolation=cv2.INTER_CUBIC)
110
 
111
+ # Draw marks with ImageJ-like yellow circle
112
  for x, y, diameter in self.marks:
 
113
  zoomed_x = int(x * self.zoom_factor)
114
  zoomed_y = int(y * self.zoom_factor)
115
  zoomed_diameter = int(diameter * self.zoom_factor)
116
 
117
+ # Draw main circle like ImageJ
118
  cv2.circle(zoomed,
119
  (zoomed_x, zoomed_y),
120
  zoomed_diameter // 2,
121
+ (0, 255, 255), # Yellow color
122
+ 1, # Thinner line
123
  lineType=cv2.LINE_AA)
124
+
125
+ # Add small points around circle perimeter (ImageJ style)
126
+ num_points = 8
127
+ for i in range(num_points):
128
+ angle = 2 * np.pi * i / num_points
129
+ point_x = int(zoomed_x + (zoomed_diameter/2) * np.cos(angle))
130
+ point_y = int(zoomed_y + (zoomed_diameter/2) * np.sin(angle))
131
+ cv2.circle(zoomed,
132
+ (point_x, point_y),
133
+ 1,
134
+ (0, 255, 255),
135
+ -1,
136
+ lineType=cv2.LINE_AA)
137
+
138
+ # Extract visible portion
139
  visible_height = min(height, new_height)
140
  visible_width = min(width, new_width)
141
 
 
170
  return self.update_display()
171
  except Exception as e:
172
  print(f"Error handling keyboard input: {str(e)}")
173
+ return self.display_image