HeshamAI commited on
Commit
24a7f4a
·
verified ·
1 Parent(s): 1f7f51a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -31
app.py CHANGED
@@ -61,6 +61,12 @@ class DicomAnalyzer:
61
  self.max_pan_x = 0
62
  self.max_pan_y = 0
63
 
 
 
 
 
 
 
64
  # Debug state
65
  self.debug_info = {
66
  'last_click': None,
@@ -82,7 +88,6 @@ class DicomAnalyzer:
82
 
83
  image = dicom_data.pixel_array.astype(float)
84
 
85
- # Apply rescale parameters if available
86
  rescale_slope = getattr(dicom_data, 'RescaleSlope', 1)
87
  rescale_intercept = getattr(dicom_data, 'RescaleIntercept', 0)
88
  image = (image * rescale_slope) + rescale_intercept
@@ -91,7 +96,6 @@ class DicomAnalyzer:
91
  self.original_image = image.copy()
92
  self.dicom_data = dicom_data
93
 
94
- # Normalize and prepare display image
95
  self.display_image = self.normalize_image(image)
96
  self.original_display = self.display_image.copy()
97
 
@@ -123,60 +127,83 @@ class DicomAnalyzer:
123
 
124
  @debug_decorator
125
  def zoom_in(self, image):
126
- self.zoom_factor = min(20.0, self.zoom_factor * 1.2)
127
- self.debug_info['zoom_history'].append(('in', self.zoom_factor))
128
- return self.update_display()
 
 
 
 
 
 
 
 
129
 
130
  @debug_decorator
131
  def zoom_out(self, image):
132
- self.zoom_factor = max(1.0, self.zoom_factor / 1.2)
133
- self.debug_info['zoom_history'].append(('out', self.zoom_factor))
134
- return self.update_display()
135
-
 
 
 
 
 
 
 
136
  @debug_decorator
137
  def analyze_roi(self, evt: gr.SelectData):
138
  try:
139
  if self.current_image is None:
140
  return None, "No image loaded"
141
 
142
- # Get click coordinates and transform them
143
  clicked_x, clicked_y = evt.index[0], evt.index[1]
144
  self.debug_info['last_click'] = (clicked_x, clicked_y)
145
 
146
- # Transform coordinates
147
  image_x = (clicked_x + self.pan_x) / self.zoom_factor
148
  image_y = (clicked_y + self.pan_y) / self.zoom_factor
149
 
150
  self.debug_info['last_transformed_coords'] = (image_x, image_y)
151
 
152
- # Create mask for ROI
153
  height, width = self.current_image.shape[:2]
154
  mask = np.zeros((height, width), dtype=np.uint8)
155
  cv2.circle(mask, (int(image_x), int(image_y)),
156
  self.circle_diameter // 2, 1, -1)
157
 
158
- # Calculate statistics
159
  roi_pixels = self.current_image[mask == 1]
160
 
161
  if len(roi_pixels) == 0:
162
  return self.display_image, "Error: No pixels selected"
163
 
164
- # Get pixel spacing
165
  pixel_spacing = getattr(self.dicom_data, 'PixelSpacing', [1.0, 1.0])[0]
166
 
167
- # Store results
 
 
 
 
 
168
  result = {
169
- 'Area (mm²)': f"{len(roi_pixels) * (pixel_spacing ** 2):.3f}",
170
- 'Mean': f"{np.mean(roi_pixels):.3f}",
171
- 'StdDev': f"{np.std(roi_pixels):.3f}",
172
- 'Min': f"{np.min(roi_pixels):.3f}",
173
- 'Max': f"{np.max(roi_pixels):.3f}",
174
  'Point': f"({image_x:.1f}, {image_y:.1f})"
175
  }
176
 
177
  self.results.append(result)
178
  self.marks.append((image_x, image_y, self.circle_diameter))
179
 
 
 
 
 
 
 
 
 
180
  return self.update_display(), self.format_results()
181
  except Exception as e:
182
  logger.error(f"Error in ROI analysis: {str(e)}")
@@ -189,22 +216,18 @@ class DicomAnalyzer:
189
  return None
190
 
191
  height, width = self.original_display.shape[:2]
192
-
193
- # Create working copy
194
  display_image = self.original_display.copy()
195
 
196
- # Draw all marks
197
  for x, y, diameter in self.marks:
198
  cv2.circle(
199
  display_image,
200
  (int(x), int(y)),
201
  diameter // 2,
202
- (0, 255, 255),
203
  1,
204
  lineType=cv2.LINE_AA
205
  )
206
 
207
- # Apply zoom
208
  if self.zoom_factor != 1.0:
209
  new_width = int(width * self.zoom_factor)
210
  new_height = int(height * self.zoom_factor)
@@ -214,15 +237,12 @@ class DicomAnalyzer:
214
  interpolation=cv2.INTER_LINEAR
215
  )
216
 
217
- # Calculate pan limits
218
  self.max_pan_x = max(0, display_image.shape[1] - width)
219
  self.max_pan_y = max(0, display_image.shape[0] - height)
220
 
221
- # Apply panning
222
  self.pan_x = min(max(0, self.pan_x), self.max_pan_x)
223
  self.pan_y = min(max(0, self.pan_y), self.max_pan_y)
224
 
225
- # Extract visible portion
226
  visible = display_image[
227
  int(self.pan_y):int(self.pan_y + height),
228
  int(self.pan_x):int(self.pan_x + width)
@@ -332,13 +352,15 @@ def create_interface():
332
  zoom_in_btn.click(
333
  fn=analyzer.zoom_in,
334
  inputs=image_display,
335
- outputs=image_display
 
336
  )
337
 
338
  zoom_out_btn.click(
339
  fn=analyzer.zoom_out,
340
  inputs=image_display,
341
- outputs=image_display
 
342
  )
343
 
344
  reset_btn.click(
@@ -361,7 +383,6 @@ def create_interface():
361
  fn=lambda: logger.debug(f"Debug Info: {analyzer.debug_info}")
362
  )
363
 
364
- # Add keyboard handling JavaScript
365
  gr.HTML("""
366
  <script>
367
  document.addEventListener('keydown', function(e) {
 
61
  self.max_pan_x = 0
62
  self.max_pan_y = 0
63
 
64
+ # Constants
65
+ self.CIRCLE_COLOR = (0, 255, 255) # BGR Yellow
66
+ self.MIN_ZOOM = 1.0
67
+ self.MAX_ZOOM = 20.0
68
+ self.ZOOM_STEP = 1.2
69
+
70
  # Debug state
71
  self.debug_info = {
72
  'last_click': None,
 
88
 
89
  image = dicom_data.pixel_array.astype(float)
90
 
 
91
  rescale_slope = getattr(dicom_data, 'RescaleSlope', 1)
92
  rescale_intercept = getattr(dicom_data, 'RescaleIntercept', 0)
93
  image = (image * rescale_slope) + rescale_intercept
 
96
  self.original_image = image.copy()
97
  self.dicom_data = dicom_data
98
 
 
99
  self.display_image = self.normalize_image(image)
100
  self.original_display = self.display_image.copy()
101
 
 
127
 
128
  @debug_decorator
129
  def zoom_in(self, image):
130
+ try:
131
+ new_zoom = self.zoom_factor * self.ZOOM_STEP
132
+ if new_zoom <= self.MAX_ZOOM:
133
+ self.zoom_factor = new_zoom
134
+ logger.debug(f"Zooming in. New zoom factor: {self.zoom_factor}")
135
+ self.debug_info['zoom_history'].append(('in', self.zoom_factor))
136
+ return self.update_display()
137
+ return image
138
+ except Exception as e:
139
+ logger.error(f"Error in zoom_in: {str(e)}")
140
+ return image
141
 
142
  @debug_decorator
143
  def zoom_out(self, image):
144
+ try:
145
+ new_zoom = self.zoom_factor / self.ZOOM_STEP
146
+ if new_zoom >= self.MIN_ZOOM:
147
+ self.zoom_factor = new_zoom
148
+ logger.debug(f"Zooming out. New zoom factor: {self.zoom_factor}")
149
+ self.debug_info['zoom_history'].append(('out', self.zoom_factor))
150
+ return self.update_display()
151
+ return image
152
+ except Exception as e:
153
+ logger.error(f"Error in zoom_out: {str(e)}")
154
+ return image
155
  @debug_decorator
156
  def analyze_roi(self, evt: gr.SelectData):
157
  try:
158
  if self.current_image is None:
159
  return None, "No image loaded"
160
 
 
161
  clicked_x, clicked_y = evt.index[0], evt.index[1]
162
  self.debug_info['last_click'] = (clicked_x, clicked_y)
163
 
 
164
  image_x = (clicked_x + self.pan_x) / self.zoom_factor
165
  image_y = (clicked_y + self.pan_y) / self.zoom_factor
166
 
167
  self.debug_info['last_transformed_coords'] = (image_x, image_y)
168
 
 
169
  height, width = self.current_image.shape[:2]
170
  mask = np.zeros((height, width), dtype=np.uint8)
171
  cv2.circle(mask, (int(image_x), int(image_y)),
172
  self.circle_diameter // 2, 1, -1)
173
 
 
174
  roi_pixels = self.current_image[mask == 1]
175
 
176
  if len(roi_pixels) == 0:
177
  return self.display_image, "Error: No pixels selected"
178
 
 
179
  pixel_spacing = getattr(self.dicom_data, 'PixelSpacing', [1.0, 1.0])[0]
180
 
181
+ mean_value = np.mean(roi_pixels)
182
+ std_dev = np.std(roi_pixels, ddof=1)
183
+ min_val = np.min(roi_pixels)
184
+ max_val = np.max(roi_pixels)
185
+ area = len(roi_pixels) * (pixel_spacing ** 2)
186
+
187
  result = {
188
+ 'Area (mm²)': f"{area:.3f}",
189
+ 'Mean': f"{mean_value:.3f}",
190
+ 'StdDev': f"{std_dev:.3f}",
191
+ 'Min': f"{min_val:.3f}",
192
+ 'Max': f"{max_val:.3f}",
193
  'Point': f"({image_x:.1f}, {image_y:.1f})"
194
  }
195
 
196
  self.results.append(result)
197
  self.marks.append((image_x, image_y, self.circle_diameter))
198
 
199
+ logger.debug(f"ROI Analysis Results:")
200
+ logger.debug(f"Position: ({image_x:.1f}, {image_y:.1f})")
201
+ logger.debug(f"Area: {area:.3f} mm²")
202
+ logger.debug(f"Mean: {mean_value:.3f}")
203
+ logger.debug(f"StdDev: {std_dev:.3f}")
204
+ logger.debug(f"Min: {min_val:.3f}")
205
+ logger.debug(f"Max: {max_val:.3f}")
206
+
207
  return self.update_display(), self.format_results()
208
  except Exception as e:
209
  logger.error(f"Error in ROI analysis: {str(e)}")
 
216
  return None
217
 
218
  height, width = self.original_display.shape[:2]
 
 
219
  display_image = self.original_display.copy()
220
 
 
221
  for x, y, diameter in self.marks:
222
  cv2.circle(
223
  display_image,
224
  (int(x), int(y)),
225
  diameter // 2,
226
+ self.CIRCLE_COLOR,
227
  1,
228
  lineType=cv2.LINE_AA
229
  )
230
 
 
231
  if self.zoom_factor != 1.0:
232
  new_width = int(width * self.zoom_factor)
233
  new_height = int(height * self.zoom_factor)
 
237
  interpolation=cv2.INTER_LINEAR
238
  )
239
 
 
240
  self.max_pan_x = max(0, display_image.shape[1] - width)
241
  self.max_pan_y = max(0, display_image.shape[0] - height)
242
 
 
243
  self.pan_x = min(max(0, self.pan_x), self.max_pan_x)
244
  self.pan_y = min(max(0, self.pan_y), self.max_pan_y)
245
 
 
246
  visible = display_image[
247
  int(self.pan_y):int(self.pan_y + height),
248
  int(self.pan_x):int(self.pan_x + width)
 
352
  zoom_in_btn.click(
353
  fn=analyzer.zoom_in,
354
  inputs=image_display,
355
+ outputs=image_display,
356
+ queue=False
357
  )
358
 
359
  zoom_out_btn.click(
360
  fn=analyzer.zoom_out,
361
  inputs=image_display,
362
+ outputs=image_display,
363
+ queue=False
364
  )
365
 
366
  reset_btn.click(
 
383
  fn=lambda: logger.debug(f"Debug Info: {analyzer.debug_info}")
384
  )
385
 
 
386
  gr.HTML("""
387
  <script>
388
  document.addEventListener('keydown', function(e) {