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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +250 -1
app.py CHANGED
@@ -170,4 +170,253 @@ class DicomAnalyzer:
170
  return self.update_display()
171
  except Exception as e:
172
  print(f"Error handling keyboard input: {str(e)}")
173
- return self.display_image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  return self.update_display()
171
  except Exception as e:
172
  print(f"Error handling keyboard input: {str(e)}")
173
+ return self.display_image
174
+ def analyze_roi(self, evt: gr.SelectData):
175
+ try:
176
+ if self.current_image is None:
177
+ return None, "No image loaded"
178
+
179
+ # Adjust coordinates to match ImageJ
180
+ height, width = self.current_image.shape[:2]
181
+ x = int(evt.index[0])
182
+ y = int(evt.index[1])
183
+
184
+ # Convert coordinates to match ImageJ system
185
+ if hasattr(self.dicom_data, 'PixelSpacing'):
186
+ pixel_spacing = float(self.dicom_data.PixelSpacing[0])
187
+ x = int(x) # Keep original x coordinate
188
+ y = int(y) # Keep original y coordinate
189
+
190
+ mask = np.zeros_like(self.current_image, dtype=np.uint8)
191
+ y_indices, x_indices = np.ogrid[:self.current_image.shape[0], :self.current_image.shape[1]]
192
+ radius = self.circle_diameter / 2
193
+ distance_from_center = np.sqrt(
194
+ (x_indices - x)**2 + (y_indices - y)**2
195
+ )
196
+ mask[distance_from_center <= radius] = 1
197
+
198
+ roi_pixels = self.current_image[mask == 1]
199
+
200
+ pixel_spacing = float(self.dicom_data.PixelSpacing[0])
201
+ area_pixels = np.sum(mask)
202
+ area_mm2 = area_pixels * (pixel_spacing ** 2)
203
+ mean = np.mean(roi_pixels)
204
+ stddev = np.std(roi_pixels)
205
+ min_val = np.min(roi_pixels)
206
+ max_val = np.max(roi_pixels)
207
+
208
+ result = {
209
+ 'Area (mm²)': f"{area_mm2:.3f}",
210
+ 'Mean': f"{mean:.3f}",
211
+ 'StdDev': f"{stddev:.3f}",
212
+ 'Min': f"{min_val:.3f}",
213
+ 'Max': f"{max_val:.3f}",
214
+ 'Point': f"({x}, {y})"
215
+ }
216
+ self.results.append(result)
217
+ self.marks.append((x, y, self.circle_diameter))
218
+ print(f"ROI analyzed at point ({x}, {y})")
219
+
220
+ return self.update_display(), self.format_results()
221
+ except Exception as e:
222
+ print(f"Error analyzing ROI: {str(e)}")
223
+ return self.display_image, f"Error analyzing ROI: {str(e)}"
224
+
225
+ def format_results(self):
226
+ if not self.results:
227
+ return "No measurements yet"
228
+ df = pd.DataFrame(self.results)
229
+ columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
230
+ df = df[columns_order]
231
+ return df.to_string(index=False)
232
+
233
+ def add_blank_row(self, image):
234
+ self.results.append({
235
+ 'Area (mm²)': '',
236
+ 'Mean': '',
237
+ 'StdDev': '',
238
+ 'Min': '',
239
+ 'Max': '',
240
+ 'Point': ''
241
+ })
242
+ return image, self.format_results()
243
+
244
+ def add_zero_row(self, image):
245
+ self.results.append({
246
+ 'Area (mm²)': '0.000',
247
+ 'Mean': '0.000',
248
+ 'StdDev': '0.000',
249
+ 'Min': '0.000',
250
+ 'Max': '0.000',
251
+ 'Point': '(0, 0)'
252
+ })
253
+ return image, self.format_results()
254
+
255
+ def undo_last(self, image):
256
+ if self.results:
257
+ self.results.pop()
258
+ if self.marks:
259
+ self.marks.pop()
260
+ return self.update_display(), self.format_results()
261
+
262
+ def save_results(self):
263
+ try:
264
+ if not self.results:
265
+ return None, "No results to save"
266
+
267
+ df = pd.DataFrame(self.results)
268
+ columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
269
+ df = df[columns_order]
270
+
271
+ temp_file = "analysis_results.xlsx"
272
+ df.to_excel(temp_file, index=False)
273
+
274
+ return temp_file, "Results saved successfully"
275
+ except Exception as e:
276
+ return None, f"Error saving results: {str(e)}"
277
+
278
+ def create_interface():
279
+ print("Creating interface...")
280
+ analyzer = DicomAnalyzer()
281
+
282
+ with gr.Blocks(css="#image_display { outline: none; }") as interface:
283
+ gr.Markdown("# DICOM Image Analyzer")
284
+
285
+ with gr.Row():
286
+ with gr.Column():
287
+ file_input = gr.File(label="Upload DICOM file")
288
+ diameter_slider = gr.Slider(
289
+ minimum=1,
290
+ maximum=20,
291
+ value=9,
292
+ step=1,
293
+ label="ROI Diameter (pixels)"
294
+ )
295
+
296
+ with gr.Row():
297
+ zoom_in_btn = gr.Button("Zoom In (+)")
298
+ zoom_out_btn = gr.Button("Zoom Out (-)")
299
+ reset_btn = gr.Button("Reset View")
300
+
301
+ with gr.Column():
302
+ image_display = gr.Image(label="DICOM Image", interactive=True, elem_id="image_display")
303
+
304
+ with gr.Row():
305
+ blank_btn = gr.Button("Add Blank Row")
306
+ zero_btn = gr.Button("Add Zero Row")
307
+ undo_btn = gr.Button("Undo Last")
308
+ save_btn = gr.Button("Save Results")
309
+
310
+ results_display = gr.Textbox(label="Results", interactive=False)
311
+ file_output = gr.File(label="Download Results")
312
+ key_press = gr.Textbox(visible=False, elem_id="key_press")
313
+
314
+ gr.Markdown("""
315
+ ### Controls:
316
+ - Use arrow keys to pan when zoomed in
317
+ - Click points to measure
318
+ - Use Zoom In/Out buttons or Reset View to adjust zoom level
319
+ """)
320
+
321
+ def update_diameter(x):
322
+ analyzer.circle_diameter = x
323
+ print(f"Diameter updated to: {x}")
324
+ return f"Diameter set to {x} pixels"
325
+
326
+ # Event handlers
327
+ file_input.change(
328
+ fn=analyzer.load_dicom,
329
+ inputs=file_input,
330
+ outputs=[image_display, results_display]
331
+ )
332
+
333
+ image_display.select(
334
+ fn=analyzer.analyze_roi,
335
+ outputs=[image_display, results_display]
336
+ )
337
+
338
+ diameter_slider.change(
339
+ fn=update_diameter,
340
+ inputs=diameter_slider,
341
+ outputs=gr.Textbox(label="Status")
342
+ )
343
+
344
+ zoom_in_btn.click(
345
+ fn=analyzer.zoom_in,
346
+ inputs=image_display,
347
+ outputs=image_display
348
+ )
349
+
350
+ zoom_out_btn.click(
351
+ fn=analyzer.zoom_out,
352
+ inputs=image_display,
353
+ outputs=image_display
354
+ )
355
+
356
+ reset_btn.click(
357
+ fn=analyzer.reset_view,
358
+ outputs=image_display
359
+ )
360
+
361
+ key_press.change(
362
+ fn=analyzer.handle_keyboard,
363
+ inputs=key_press,
364
+ outputs=image_display
365
+ )
366
+
367
+ blank_btn.click(
368
+ fn=analyzer.add_blank_row,
369
+ inputs=image_display,
370
+ outputs=[image_display, results_display]
371
+ )
372
+
373
+ zero_btn.click(
374
+ fn=analyzer.add_zero_row,
375
+ inputs=image_display,
376
+ outputs=[image_display, results_display]
377
+ )
378
+
379
+ undo_btn.click(
380
+ fn=analyzer.undo_last,
381
+ inputs=image_display,
382
+ outputs=[image_display, results_display]
383
+ )
384
+
385
+ save_btn.click(
386
+ fn=analyzer.save_results,
387
+ outputs=[file_output, results_display]
388
+ )
389
+
390
+ js = """
391
+ <script>
392
+ document.addEventListener('keydown', function(e) {
393
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
394
+ e.preventDefault();
395
+ const keyPressElement = document.querySelector('#key_press textarea');
396
+ if (keyPressElement) {
397
+ keyPressElement.value = e.key;
398
+ keyPressElement.dispatchEvent(new Event('input'));
399
+ }
400
+ }
401
+ });
402
+ </script>
403
+ """
404
+ gr.HTML(js)
405
+
406
+ print("Interface created successfully")
407
+ return interface
408
+
409
+ if __name__ == "__main__":
410
+ try:
411
+ print("Starting application...")
412
+ interface = create_interface()
413
+ print("Launching interface...")
414
+ interface.launch(
415
+ server_name="0.0.0.0",
416
+ server_port=7860,
417
+ share=True,
418
+ debug=True
419
+ )
420
+ except Exception as e:
421
+ print(f"Error launching application: {str(e)}")
422
+ raise e