Update app.py
Browse files
app.py
CHANGED
|
@@ -5,9 +5,46 @@ import pandas as pd
|
|
| 5 |
import pydicom
|
| 6 |
import io
|
| 7 |
from PIL import Image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
print("Starting imports completed...")
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
class DicomAnalyzer:
|
| 12 |
def __init__(self):
|
| 13 |
self.results = []
|
|
@@ -124,17 +161,14 @@ class DicomAnalyzer:
|
|
| 124 |
except Exception as e:
|
| 125 |
print(f"Error handling keyboard input: {str(e)}")
|
| 126 |
return self.display_image
|
| 127 |
-
|
| 128 |
def analyze_roi(self, evt: gr.SelectData):
|
| 129 |
try:
|
| 130 |
if self.current_image is None:
|
| 131 |
return None, "No image loaded"
|
| 132 |
|
| 133 |
-
# Get clicked coordinates
|
| 134 |
clicked_x = evt.index[0]
|
| 135 |
clicked_y = evt.index[1]
|
| 136 |
|
| 137 |
-
# Transform coordinates
|
| 138 |
x = clicked_x + self.pan_x
|
| 139 |
y = clicked_y + self.pan_y
|
| 140 |
if self.zoom_factor != 1.0:
|
|
@@ -144,63 +178,43 @@ class DicomAnalyzer:
|
|
| 144 |
x = int(round(x))
|
| 145 |
y = int(round(y))
|
| 146 |
|
| 147 |
-
# Get image dimensions
|
| 148 |
height, width = self.original_image.shape[:2]
|
| 149 |
|
| 150 |
-
# Create mask using ImageJ's exact method
|
| 151 |
Y, X = np.ogrid[:height, :width]
|
| 152 |
|
| 153 |
-
# Use exact 9-pixel diameter
|
| 154 |
radius = self.circle_diameter / 2.0
|
| 155 |
r_squared = radius * radius
|
| 156 |
|
| 157 |
-
# Calculate distances exactly as ImageJ does
|
| 158 |
dx = X - x
|
| 159 |
dy = Y - y
|
| 160 |
dist_squared = dx*dx + dy*dy
|
| 161 |
|
| 162 |
-
# Create mask with ImageJ's method
|
| 163 |
mask = np.zeros((height, width), dtype=bool)
|
| 164 |
mask[dist_squared <= r_squared] = True
|
| 165 |
|
| 166 |
-
# Get ROI pixels from original DICOM values
|
| 167 |
roi_pixels = self.original_image[mask]
|
| 168 |
|
| 169 |
if len(roi_pixels) == 0:
|
| 170 |
return self.display_image, "Error: No pixels selected"
|
| 171 |
|
| 172 |
-
# Get pixel spacing (mm/pixel)
|
| 173 |
pixel_spacing = float(self.dicom_data.PixelSpacing[0])
|
| 174 |
|
| 175 |
-
# Calculate area (this part is correct)
|
| 176 |
n_pixels = np.sum(mask)
|
| 177 |
area = n_pixels * (pixel_spacing ** 2)
|
| 178 |
|
| 179 |
-
# Calculate statistics using ImageJ's methods
|
| 180 |
mean_value = np.mean(roi_pixels)
|
| 181 |
-
std_dev = np.std(roi_pixels, ddof=1)
|
| 182 |
min_val = np.min(roi_pixels)
|
| 183 |
max_val = np.max(roi_pixels)
|
| 184 |
|
| 185 |
-
# Apply any necessary scaling from DICOM
|
| 186 |
rescale_slope = getattr(self.dicom_data, 'RescaleSlope', 1)
|
| 187 |
rescale_intercept = getattr(self.dicom_data, 'RescaleIntercept', 0)
|
| 188 |
|
| 189 |
-
# Adjust values using DICOM scaling
|
| 190 |
mean_value = (mean_value * rescale_slope) + rescale_intercept
|
| 191 |
std_dev = std_dev * rescale_slope
|
| 192 |
min_val = (min_val * rescale_slope) + rescale_intercept
|
| 193 |
max_val = (max_val * rescale_slope) + rescale_intercept
|
| 194 |
|
| 195 |
-
print(f"\nImageJ-compatible Analysis:")
|
| 196 |
-
print(f"Position: ({x}, {y})")
|
| 197 |
-
print(f"Pixel count: {n_pixels}")
|
| 198 |
-
print(f"Area: {area:.3f} mm²")
|
| 199 |
-
print(f"Mean: {mean_value:.3f}")
|
| 200 |
-
print(f"StdDev: {std_dev:.3f}")
|
| 201 |
-
print(f"Min: {min_val}")
|
| 202 |
-
print(f"Max: {max_val}")
|
| 203 |
-
|
| 204 |
result = {
|
| 205 |
'Area (mm²)': f"{area:.3f}",
|
| 206 |
'Mean': f"{mean_value:.3f}",
|
|
@@ -217,6 +231,7 @@ class DicomAnalyzer:
|
|
| 217 |
except Exception as e:
|
| 218 |
print(f"Error analyzing ROI: {str(e)}")
|
| 219 |
return self.display_image, f"Error analyzing ROI: {str(e)}"
|
|
|
|
| 220 |
def update_display(self):
|
| 221 |
try:
|
| 222 |
if self.original_display is None:
|
|
@@ -226,29 +241,23 @@ class DicomAnalyzer:
|
|
| 226 |
new_height = int(height * self.zoom_factor)
|
| 227 |
new_width = int(width * self.zoom_factor)
|
| 228 |
|
| 229 |
-
# Create zoomed image
|
| 230 |
zoomed = cv2.resize(self.original_display, (new_width, new_height),
|
| 231 |
interpolation=cv2.INTER_CUBIC)
|
| 232 |
|
| 233 |
-
# Convert to BGR for drawing
|
| 234 |
zoomed_bgr = cv2.cvtColor(zoomed, cv2.COLOR_RGB2BGR)
|
| 235 |
|
| 236 |
-
# Draw marks with ImageJ-style dots
|
| 237 |
for x, y, diameter in self.marks:
|
| 238 |
zoomed_x = int(x * self.zoom_factor)
|
| 239 |
zoomed_y = int(y * self.zoom_factor)
|
| 240 |
-
# Use exact radius without any additions
|
| 241 |
zoomed_radius = int((diameter/2.0) * self.zoom_factor)
|
| 242 |
|
| 243 |
-
# Draw main circle
|
| 244 |
cv2.circle(zoomed_bgr,
|
| 245 |
(zoomed_x, zoomed_y),
|
| 246 |
zoomed_radius,
|
| 247 |
-
self.CIRCLE_COLOR,
|
| 248 |
1,
|
| 249 |
lineType=cv2.LINE_AA)
|
| 250 |
|
| 251 |
-
# Draw dots like ImageJ
|
| 252 |
num_points = 8
|
| 253 |
for i in range(num_points):
|
| 254 |
angle = 2 * np.pi * i / num_points
|
|
@@ -261,16 +270,13 @@ class DicomAnalyzer:
|
|
| 261 |
-1,
|
| 262 |
lineType=cv2.LINE_AA)
|
| 263 |
|
| 264 |
-
# Convert back to RGB for display
|
| 265 |
zoomed = cv2.cvtColor(zoomed_bgr, cv2.COLOR_BGR2RGB)
|
| 266 |
|
| 267 |
-
# Calculate pan limits
|
| 268 |
self.max_pan_x = max(0, new_width - width)
|
| 269 |
self.max_pan_y = max(0, new_height - height)
|
| 270 |
self.pan_x = min(max(0, self.pan_x), self.max_pan_x)
|
| 271 |
self.pan_y = min(max(0, self.pan_y), self.max_pan_y)
|
| 272 |
|
| 273 |
-
# Extract visible portion
|
| 274 |
visible = zoomed[
|
| 275 |
int(self.pan_y):int(self.pan_y + height),
|
| 276 |
int(self.pan_x):int(self.pan_x + width)
|
|
@@ -289,6 +295,79 @@ class DicomAnalyzer:
|
|
| 289 |
df = df[columns_order]
|
| 290 |
return df.to_string(index=False)
|
| 291 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
def add_blank_row(self, image):
|
| 293 |
self.results.append({
|
| 294 |
'Area (mm²)': '',
|
|
@@ -318,22 +397,7 @@ class DicomAnalyzer:
|
|
| 318 |
self.marks.pop()
|
| 319 |
return self.update_display(), self.format_results()
|
| 320 |
|
| 321 |
-
|
| 322 |
-
try:
|
| 323 |
-
if not self.results:
|
| 324 |
-
return None, "No results to save"
|
| 325 |
-
|
| 326 |
-
df = pd.DataFrame(self.results)
|
| 327 |
-
columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point']
|
| 328 |
-
df = df[columns_order]
|
| 329 |
-
|
| 330 |
-
temp_file = "analysis_results.xlsx"
|
| 331 |
-
df.to_excel(temp_file, index=False)
|
| 332 |
-
|
| 333 |
-
return temp_file, "Results saved successfully"
|
| 334 |
-
except Exception as e:
|
| 335 |
-
return None, f"Error saving results: {str(e)}"
|
| 336 |
-
|
| 337 |
def create_interface():
|
| 338 |
print("Creating interface...")
|
| 339 |
analyzer = DicomAnalyzer()
|
|
@@ -358,7 +422,11 @@ def create_interface():
|
|
| 358 |
reset_btn = gr.Button("Reset View")
|
| 359 |
|
| 360 |
with gr.Column():
|
| 361 |
-
image_display = gr.Image(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
with gr.Row():
|
| 364 |
blank_btn = gr.Button("Add Blank Row")
|
|
@@ -375,6 +443,7 @@ def create_interface():
|
|
| 375 |
- Use arrow keys to pan when zoomed in
|
| 376 |
- Click points to measure
|
| 377 |
- Use Zoom In/Out buttons or Reset View to adjust zoom level
|
|
|
|
| 378 |
""")
|
| 379 |
|
| 380 |
def update_diameter(x):
|
|
@@ -403,13 +472,15 @@ def create_interface():
|
|
| 403 |
zoom_in_btn.click(
|
| 404 |
fn=analyzer.zoom_in,
|
| 405 |
inputs=image_display,
|
| 406 |
-
outputs=image_display
|
|
|
|
| 407 |
)
|
| 408 |
|
| 409 |
zoom_out_btn.click(
|
| 410 |
fn=analyzer.zoom_out,
|
| 411 |
inputs=image_display,
|
| 412 |
-
outputs=image_display
|
|
|
|
| 413 |
)
|
| 414 |
|
| 415 |
reset_btn.click(
|
|
@@ -478,4 +549,6 @@ if __name__ == "__main__":
|
|
| 478 |
)
|
| 479 |
except Exception as e:
|
| 480 |
print(f"Error launching application: {str(e)}")
|
|
|
|
|
|
|
| 481 |
raise e
|
|
|
|
| 5 |
import pydicom
|
| 6 |
import io
|
| 7 |
from PIL import Image
|
| 8 |
+
import openpyxl
|
| 9 |
+
from openpyxl.utils import get_column_letter, column_index_from_string
|
| 10 |
+
import logging
|
| 11 |
+
import time
|
| 12 |
+
import traceback
|
| 13 |
+
from functools import wraps
|
| 14 |
+
import sys
|
| 15 |
|
| 16 |
print("Starting imports completed...")
|
| 17 |
|
| 18 |
+
# Set up logging
|
| 19 |
+
logging.basicConfig(
|
| 20 |
+
level=logging.DEBUG,
|
| 21 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
| 22 |
+
handlers=[
|
| 23 |
+
logging.FileHandler('dicom_analyzer_debug.log'),
|
| 24 |
+
logging.StreamHandler(sys.stdout)
|
| 25 |
+
]
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
def debug_decorator(func):
|
| 31 |
+
@wraps(func)
|
| 32 |
+
def wrapper(*args, **kwargs):
|
| 33 |
+
logger.debug(f"Entering {func.__name__}")
|
| 34 |
+
start_time = time.time()
|
| 35 |
+
try:
|
| 36 |
+
result = func(*args, **kwargs)
|
| 37 |
+
logger.debug(f"Function {func.__name__} completed successfully")
|
| 38 |
+
return result
|
| 39 |
+
except Exception as e:
|
| 40 |
+
logger.error(f"Error in {func.__name__}: {str(e)}")
|
| 41 |
+
logger.error(traceback.format_exc())
|
| 42 |
+
raise
|
| 43 |
+
finally:
|
| 44 |
+
end_time = time.time()
|
| 45 |
+
logger.debug(f"Execution time: {end_time - start_time:.4f} seconds")
|
| 46 |
+
return wrapper
|
| 47 |
+
|
| 48 |
class DicomAnalyzer:
|
| 49 |
def __init__(self):
|
| 50 |
self.results = []
|
|
|
|
| 161 |
except Exception as e:
|
| 162 |
print(f"Error handling keyboard input: {str(e)}")
|
| 163 |
return self.display_image
|
|
|
|
| 164 |
def analyze_roi(self, evt: gr.SelectData):
|
| 165 |
try:
|
| 166 |
if self.current_image is None:
|
| 167 |
return None, "No image loaded"
|
| 168 |
|
|
|
|
| 169 |
clicked_x = evt.index[0]
|
| 170 |
clicked_y = evt.index[1]
|
| 171 |
|
|
|
|
| 172 |
x = clicked_x + self.pan_x
|
| 173 |
y = clicked_y + self.pan_y
|
| 174 |
if self.zoom_factor != 1.0:
|
|
|
|
| 178 |
x = int(round(x))
|
| 179 |
y = int(round(y))
|
| 180 |
|
|
|
|
| 181 |
height, width = self.original_image.shape[:2]
|
| 182 |
|
|
|
|
| 183 |
Y, X = np.ogrid[:height, :width]
|
| 184 |
|
|
|
|
| 185 |
radius = self.circle_diameter / 2.0
|
| 186 |
r_squared = radius * radius
|
| 187 |
|
|
|
|
| 188 |
dx = X - x
|
| 189 |
dy = Y - y
|
| 190 |
dist_squared = dx*dx + dy*dy
|
| 191 |
|
|
|
|
| 192 |
mask = np.zeros((height, width), dtype=bool)
|
| 193 |
mask[dist_squared <= r_squared] = True
|
| 194 |
|
|
|
|
| 195 |
roi_pixels = self.original_image[mask]
|
| 196 |
|
| 197 |
if len(roi_pixels) == 0:
|
| 198 |
return self.display_image, "Error: No pixels selected"
|
| 199 |
|
|
|
|
| 200 |
pixel_spacing = float(self.dicom_data.PixelSpacing[0])
|
| 201 |
|
|
|
|
| 202 |
n_pixels = np.sum(mask)
|
| 203 |
area = n_pixels * (pixel_spacing ** 2)
|
| 204 |
|
|
|
|
| 205 |
mean_value = np.mean(roi_pixels)
|
| 206 |
+
std_dev = np.std(roi_pixels, ddof=1)
|
| 207 |
min_val = np.min(roi_pixels)
|
| 208 |
max_val = np.max(roi_pixels)
|
| 209 |
|
|
|
|
| 210 |
rescale_slope = getattr(self.dicom_data, 'RescaleSlope', 1)
|
| 211 |
rescale_intercept = getattr(self.dicom_data, 'RescaleIntercept', 0)
|
| 212 |
|
|
|
|
| 213 |
mean_value = (mean_value * rescale_slope) + rescale_intercept
|
| 214 |
std_dev = std_dev * rescale_slope
|
| 215 |
min_val = (min_val * rescale_slope) + rescale_intercept
|
| 216 |
max_val = (max_val * rescale_slope) + rescale_intercept
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
result = {
|
| 219 |
'Area (mm²)': f"{area:.3f}",
|
| 220 |
'Mean': f"{mean_value:.3f}",
|
|
|
|
| 231 |
except Exception as e:
|
| 232 |
print(f"Error analyzing ROI: {str(e)}")
|
| 233 |
return self.display_image, f"Error analyzing ROI: {str(e)}"
|
| 234 |
+
|
| 235 |
def update_display(self):
|
| 236 |
try:
|
| 237 |
if self.original_display is None:
|
|
|
|
| 241 |
new_height = int(height * self.zoom_factor)
|
| 242 |
new_width = int(width * self.zoom_factor)
|
| 243 |
|
|
|
|
| 244 |
zoomed = cv2.resize(self.original_display, (new_width, new_height),
|
| 245 |
interpolation=cv2.INTER_CUBIC)
|
| 246 |
|
|
|
|
| 247 |
zoomed_bgr = cv2.cvtColor(zoomed, cv2.COLOR_RGB2BGR)
|
| 248 |
|
|
|
|
| 249 |
for x, y, diameter in self.marks:
|
| 250 |
zoomed_x = int(x * self.zoom_factor)
|
| 251 |
zoomed_y = int(y * self.zoom_factor)
|
|
|
|
| 252 |
zoomed_radius = int((diameter/2.0) * self.zoom_factor)
|
| 253 |
|
|
|
|
| 254 |
cv2.circle(zoomed_bgr,
|
| 255 |
(zoomed_x, zoomed_y),
|
| 256 |
zoomed_radius,
|
| 257 |
+
self.CIRCLE_COLOR,
|
| 258 |
1,
|
| 259 |
lineType=cv2.LINE_AA)
|
| 260 |
|
|
|
|
| 261 |
num_points = 8
|
| 262 |
for i in range(num_points):
|
| 263 |
angle = 2 * np.pi * i / num_points
|
|
|
|
| 270 |
-1,
|
| 271 |
lineType=cv2.LINE_AA)
|
| 272 |
|
|
|
|
| 273 |
zoomed = cv2.cvtColor(zoomed_bgr, cv2.COLOR_BGR2RGB)
|
| 274 |
|
|
|
|
| 275 |
self.max_pan_x = max(0, new_width - width)
|
| 276 |
self.max_pan_y = max(0, new_height - height)
|
| 277 |
self.pan_x = min(max(0, self.pan_x), self.max_pan_x)
|
| 278 |
self.pan_y = min(max(0, self.pan_y), self.max_pan_y)
|
| 279 |
|
|
|
|
| 280 |
visible = zoomed[
|
| 281 |
int(self.pan_y):int(self.pan_y + height),
|
| 282 |
int(self.pan_x):int(self.pan_x + width)
|
|
|
|
| 295 |
df = df[columns_order]
|
| 296 |
return df.to_string(index=False)
|
| 297 |
|
| 298 |
+
def save_results(self):
|
| 299 |
+
try:
|
| 300 |
+
if not self.results:
|
| 301 |
+
return None, "No results to save"
|
| 302 |
+
|
| 303 |
+
# Create a new workbook
|
| 304 |
+
wb = openpyxl.Workbook()
|
| 305 |
+
ws = wb.active
|
| 306 |
+
|
| 307 |
+
# Define the equation slots
|
| 308 |
+
equation_slots = [
|
| 309 |
+
('B', 'F'), ('H', 'L'), ('N', 'R'), ('T', 'X'), ('Z', 'AD'),
|
| 310 |
+
('AF', 'AJ'), ('AL', 'AP'), ('AR', 'AV'), ('AX', 'BB'), ('BD', 'BH'),
|
| 311 |
+
('BJ', 'BN'), ('BP', 'BT'), ('BV', 'BZ'),
|
| 312 |
+
]
|
| 313 |
+
|
| 314 |
+
# Define row groups
|
| 315 |
+
row_groups = [
|
| 316 |
+
(2, 3), (5, 6), (8, 9), (11, 12), (14, 15),
|
| 317 |
+
(17, 18), (20, 21), (23, 24), (26, 27), (29, 30),
|
| 318 |
+
]
|
| 319 |
+
|
| 320 |
+
# Add headers for different phantom sizes
|
| 321 |
+
phantom_sizes = ['(7mm)', '(6.5mm)', '(6mm)', '(5.5mm)', '(5mm)', '(4.5mm)']
|
| 322 |
+
for i, size in enumerate(phantom_sizes):
|
| 323 |
+
row_index = row_groups[i][0] - 1
|
| 324 |
+
ws.cell(row=row_index, column=1, value=size)
|
| 325 |
+
|
| 326 |
+
# Process results in pairs
|
| 327 |
+
result_pairs = [self.results[i:i+2] for i in range(0, len(self.results), 2)]
|
| 328 |
+
|
| 329 |
+
for pair_idx, result_pair in enumerate(result_pairs):
|
| 330 |
+
if pair_idx >= len(equation_slots) * len(row_groups):
|
| 331 |
+
break
|
| 332 |
+
|
| 333 |
+
slot_idx = pair_idx % len(equation_slots)
|
| 334 |
+
group_idx = pair_idx // len(equation_slots)
|
| 335 |
+
|
| 336 |
+
if group_idx >= len(row_groups):
|
| 337 |
+
break
|
| 338 |
+
|
| 339 |
+
start_col, _ = equation_slots[slot_idx]
|
| 340 |
+
dest_rows = row_groups[group_idx]
|
| 341 |
+
|
| 342 |
+
# Fill data for the pair
|
| 343 |
+
for row_idx, result in enumerate(result_pair):
|
| 344 |
+
if row_idx < 2: # Only process up to 2 rows
|
| 345 |
+
dest_row = dest_rows[row_idx]
|
| 346 |
+
|
| 347 |
+
# Write row number
|
| 348 |
+
ws.cell(row=dest_row, column=1, value=row_idx + 1)
|
| 349 |
+
|
| 350 |
+
# Write values in correct columns
|
| 351 |
+
ws.cell(row=dest_row, column=openpyxl.utils.column_index_from_string(start_col),
|
| 352 |
+
value=float(result['Area (mm²)']))
|
| 353 |
+
ws.cell(row=dest_row, column=openpyxl.utils.column_index_from_string(start_col) + 1,
|
| 354 |
+
value=float(result['Mean']))
|
| 355 |
+
ws.cell(row=dest_row, column=openpyxl.utils.column_index_from_string(start_col) + 2,
|
| 356 |
+
value=float(result['StdDev']))
|
| 357 |
+
ws.cell(row=dest_row, column=openpyxl.utils.column_index_from_string(start_col) + 3,
|
| 358 |
+
value=float(result['Min']))
|
| 359 |
+
ws.cell(row=dest_row, column=openpyxl.utils.column_index_from_string(start_col) + 4,
|
| 360 |
+
value=float(result['Max']))
|
| 361 |
+
|
| 362 |
+
# Save the workbook
|
| 363 |
+
output_file = "analysis_results.xlsx"
|
| 364 |
+
wb.save(output_file)
|
| 365 |
+
|
| 366 |
+
return output_file, "Results saved successfully in the required format"
|
| 367 |
+
except Exception as e:
|
| 368 |
+
print(f"Error saving results: {str(e)}")
|
| 369 |
+
return None, f"Error saving results: {str(e)}"
|
| 370 |
+
|
| 371 |
def add_blank_row(self, image):
|
| 372 |
self.results.append({
|
| 373 |
'Area (mm²)': '',
|
|
|
|
| 397 |
self.marks.pop()
|
| 398 |
return self.update_display(), self.format_results()
|
| 399 |
|
| 400 |
+
# ... (rest of the code with create_interface and main remains the same)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
def create_interface():
|
| 402 |
print("Creating interface...")
|
| 403 |
analyzer = DicomAnalyzer()
|
|
|
|
| 422 |
reset_btn = gr.Button("Reset View")
|
| 423 |
|
| 424 |
with gr.Column():
|
| 425 |
+
image_display = gr.Image(
|
| 426 |
+
label="DICOM Image",
|
| 427 |
+
interactive=True,
|
| 428 |
+
elem_id="image_display"
|
| 429 |
+
)
|
| 430 |
|
| 431 |
with gr.Row():
|
| 432 |
blank_btn = gr.Button("Add Blank Row")
|
|
|
|
| 443 |
- Use arrow keys to pan when zoomed in
|
| 444 |
- Click points to measure
|
| 445 |
- Use Zoom In/Out buttons or Reset View to adjust zoom level
|
| 446 |
+
- Results will be saved in ImageJ-compatible format
|
| 447 |
""")
|
| 448 |
|
| 449 |
def update_diameter(x):
|
|
|
|
| 472 |
zoom_in_btn.click(
|
| 473 |
fn=analyzer.zoom_in,
|
| 474 |
inputs=image_display,
|
| 475 |
+
outputs=image_display,
|
| 476 |
+
queue=False # Allow continuous clicking
|
| 477 |
)
|
| 478 |
|
| 479 |
zoom_out_btn.click(
|
| 480 |
fn=analyzer.zoom_out,
|
| 481 |
inputs=image_display,
|
| 482 |
+
outputs=image_display,
|
| 483 |
+
queue=False # Allow continuous clicking
|
| 484 |
)
|
| 485 |
|
| 486 |
reset_btn.click(
|
|
|
|
| 549 |
)
|
| 550 |
except Exception as e:
|
| 551 |
print(f"Error launching application: {str(e)}")
|
| 552 |
+
logger.error(f"Error launching application: {str(e)}")
|
| 553 |
+
logger.error(traceback.format_exc())
|
| 554 |
raise e
|