HeshamAI commited on
Commit
afa65ec
·
verified ·
1 Parent(s): ff23b91

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +45 -28
app.py CHANGED
@@ -28,6 +28,23 @@ class DicomAnalyzer:
28
  self.CIRCLE_COLOR = (0, 255, 255) # BGR Yellow
29
  print("DicomAnalyzer initialized...")
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def load_dicom(self, file):
32
  try:
33
  if file is None:
@@ -127,28 +144,25 @@ class DicomAnalyzer:
127
  if self.current_image is None:
128
  return None, "No image loaded"
129
 
130
- # Get clicked coordinates
131
- clicked_x, clicked_y = evt.index[0], evt.index[1]
132
-
133
- # Transform coordinates to match ImageJ
134
- x = (clicked_x + self.pan_x) / self.zoom_factor
135
- y = (clicked_y + self.pan_y) / self.zoom_factor
136
 
137
  # Get image dimensions
138
  height, width = self.current_image.shape[:2]
139
 
140
- # ImageJ-style circle creation
141
  Y, X = np.ogrid[:height, :width]
142
 
143
- # ImageJ uses a specific method for radius calculation
144
- radius = self.circle_diameter / 2.0
145
 
146
- # Create the circular ROI mask using ImageJ's method
147
- # ImageJ considers pixels as 1x1 squares centered on integer coordinates
148
- dist_squared = (X - x)**2 + (Y - y)**2
149
- mask = dist_squared <= (radius * radius)
 
150
 
151
- # Get ROI pixels
152
  roi_pixels = self.current_image[mask]
153
 
154
  if len(roi_pixels) == 0:
@@ -158,15 +172,26 @@ class DicomAnalyzer:
158
  pixel_spacing = float(self.dicom_data.PixelSpacing[0])
159
 
160
  # Calculate area exactly as ImageJ does
161
- n_pixels = np.sum(mask) # Total number of pixels in ROI
162
- area = n_pixels * (pixel_spacing * pixel_spacing) # Area in mm²
163
 
164
- # Calculate other statistics
165
  mean_value = np.mean(roi_pixels)
166
- std_dev = np.std(roi_pixels, ddof=1) # ImageJ uses n-1 for standard deviation
167
  min_val = np.min(roi_pixels)
168
  max_val = np.max(roi_pixels)
169
 
 
 
 
 
 
 
 
 
 
 
 
170
  # Store results
171
  result = {
172
  'Area (mm²)': f"{area:.3f}",
@@ -174,17 +199,9 @@ class DicomAnalyzer:
174
  'StdDev': f"{std_dev:.3f}",
175
  'Min': f"{min_val:.3f}",
176
  'Max': f"{max_val:.3f}",
177
- 'Point': f"({x:.1f}, {y:.1f})"
178
  }
179
 
180
- print(f"ROI Analysis Results:")
181
- print(f"Number of pixels: {n_pixels}")
182
- print(f"Pixel spacing: {pixel_spacing} mm")
183
- print(f"Area: {area:.3f} mm²")
184
- print(f"Mean: {mean_value:.3f}")
185
- print(f"StdDev: {std_dev:.3f}")
186
- print(f"Position: ({x:.1f}, {y:.1f})")
187
-
188
  self.results.append(result)
189
  self.marks.append((x, y, self.circle_diameter))
190
 
@@ -212,7 +229,7 @@ class DicomAnalyzer:
212
  for x, y, diameter in self.marks:
213
  zoomed_x = int(x * self.zoom_factor)
214
  zoomed_y = int(y * self.zoom_factor)
215
- zoomed_radius = int((diameter/2) * self.zoom_factor)
216
 
217
  # Draw main circle
218
  cv2.circle(zoomed_bgr,
 
28
  self.CIRCLE_COLOR = (0, 255, 255) # BGR Yellow
29
  print("DicomAnalyzer initialized...")
30
 
31
+ def transform_coordinates(self, clicked_x, clicked_y):
32
+ """Transform screen coordinates to image coordinates using ImageJ method"""
33
+ # Transform from screen to image coordinates
34
+ x = clicked_x + self.pan_x
35
+ y = clicked_y + self.pan_y
36
+
37
+ # Apply zoom factor
38
+ if self.zoom_factor != 1.0:
39
+ x = x / self.zoom_factor
40
+ y = y / self.zoom_factor
41
+
42
+ # Round to nearest integer to match ImageJ behavior
43
+ x = round(x)
44
+ y = round(y)
45
+
46
+ return x, y
47
+
48
  def load_dicom(self, file):
49
  try:
50
  if file is None:
 
144
  if self.current_image is None:
145
  return None, "No image loaded"
146
 
147
+ # Transform coordinates using ImageJ method
148
+ x, y = self.transform_coordinates(evt.index[0], evt.index[1])
 
 
 
 
149
 
150
  # Get image dimensions
151
  height, width = self.current_image.shape[:2]
152
 
153
+ # Create precise circular mask using ImageJ's method
154
  Y, X = np.ogrid[:height, :width]
155
 
156
+ # ImageJ's circle creation method
157
+ radius = (self.circle_diameter - 1) / 2.0
158
 
159
+ # Calculate distances using ImageJ's method
160
+ dx = X - x
161
+ dy = Y - y
162
+ dist_squared = dx*dx + dy*dy
163
+ mask = dist_squared <= radius*radius
164
 
165
+ # Get ROI pixels using the exact same mask
166
  roi_pixels = self.current_image[mask]
167
 
168
  if len(roi_pixels) == 0:
 
172
  pixel_spacing = float(self.dicom_data.PixelSpacing[0])
173
 
174
  # Calculate area exactly as ImageJ does
175
+ n_pixels = np.sum(mask)
176
+ area = n_pixels * (pixel_spacing * pixel_spacing)
177
 
178
+ # Calculate statistics exactly as ImageJ does
179
  mean_value = np.mean(roi_pixels)
180
+ std_dev = np.std(roi_pixels, ddof=1) # ImageJ uses n-1
181
  min_val = np.min(roi_pixels)
182
  max_val = np.max(roi_pixels)
183
 
184
+ # Debug print
185
+ print(f"\nImageJ-compatible Analysis:")
186
+ print(f"Position: ({x}, {y})")
187
+ print(f"Radius: {radius}")
188
+ print(f"Pixel count: {n_pixels}")
189
+ print(f"Area: {area:.3f} mm²")
190
+ print(f"Mean: {mean_value:.3f}")
191
+ print(f"StdDev: {std_dev:.3f}")
192
+ print(f"Min: {min_val}")
193
+ print(f"Max: {max_val}")
194
+
195
  # Store results
196
  result = {
197
  'Area (mm²)': f"{area:.3f}",
 
199
  'StdDev': f"{std_dev:.3f}",
200
  'Min': f"{min_val:.3f}",
201
  'Max': f"{max_val:.3f}",
202
+ 'Point': f"({x}, {y})"
203
  }
204
 
 
 
 
 
 
 
 
 
205
  self.results.append(result)
206
  self.marks.append((x, y, self.circle_diameter))
207
 
 
229
  for x, y, diameter in self.marks:
230
  zoomed_x = int(x * self.zoom_factor)
231
  zoomed_y = int(y * self.zoom_factor)
232
+ zoomed_radius = int(((diameter - 1) / 2) * self.zoom_factor) # ImageJ radius
233
 
234
  # Draw main circle
235
  cv2.circle(zoomed_bgr,