| | import gradio as gr |
| | import cv2 |
| | import numpy as np |
| | import pandas as pd |
| | import pydicom |
| | import io |
| | from PIL import Image |
| | import openpyxl |
| | from openpyxl.utils import get_column_letter, column_index_from_string |
| | import logging |
| | import time |
| | import traceback |
| | from functools import wraps |
| | import sys |
| |
|
| | print("Starting imports completed...") |
| |
|
| | |
| | logging.basicConfig( |
| | level=logging.DEBUG, |
| | format='%(asctime)s - %(levelname)s - %(message)s', |
| | handlers=[ |
| | logging.FileHandler('dicom_analyzer_debug.log'), |
| | logging.StreamHandler(sys.stdout) |
| | ] |
| | ) |
| |
|
| | logger = logging.getLogger(__name__) |
| |
|
| | def debug_decorator(func): |
| | @wraps(func) |
| | def wrapper(*args, **kwargs): |
| | logger.debug(f"Entering {func.__name__}") |
| | start_time = time.time() |
| | try: |
| | result = func(*args, **kwargs) |
| | logger.debug(f"Function {func.__name__} completed successfully") |
| | return result |
| | except Exception as e: |
| | logger.error(f"Error in {func.__name__}: {str(e)}") |
| | logger.error(traceback.format_exc()) |
| | raise |
| | finally: |
| | end_time = time.time() |
| | logger.debug(f"Execution time: {end_time - start_time:.4f} seconds") |
| | return wrapper |
| |
|
| |
|
| | class DicomAnalyzer: |
| | def __init__(self): |
| | self.results = [] |
| | self.circle_diameter = 9.0 |
| | self.zoom_factor = 1.0 |
| | self.current_image = None |
| | self.dicom_data = None |
| | self.display_image = None |
| | self.marks = [] |
| | self.original_image = None |
| | self.original_display = None |
| | self.pan_x = 0 |
| | self.pan_y = 0 |
| | self.max_pan_x = 0 |
| | self.max_pan_y = 0 |
| | self.CIRCLE_COLOR = (0, 255, 255) |
| | self.SMALL_CIRCLES_COLOR = (255, 255, 255) |
| | print("DicomAnalyzer initialized...") |
| |
|
| | def save_results(self): |
| | try: |
| | if not self.results: |
| | logger.warning("Attempted to save with no results") |
| | return None, "No results to save" |
| |
|
| | df = pd.DataFrame(self.results) |
| | columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point'] |
| | df = df[columns_order] |
| |
|
| | timestamp = time.strftime("%Y%m%d_%H%M%S") |
| | output_file = f"analysis_results_{timestamp}.xlsx" |
| |
|
| | with pd.ExcelWriter(output_file, engine='openpyxl') as writer: |
| | df.to_excel(writer, index=False, sheet_name='Results') |
| | |
| | worksheet = writer.sheets['Results'] |
| | for idx, col in enumerate(df.columns): |
| | max_length = max( |
| | df[col].astype(str).apply(len).max(), |
| | len(str(col)) |
| | ) + 2 |
| | worksheet.column_dimensions[get_column_letter(idx + 1)].width = max_length |
| |
|
| | logger.info(f"Results saved successfully to {output_file}") |
| | return output_file, f"Results saved successfully to {output_file}" |
| |
|
| | except Exception as e: |
| | error_msg = f"Error saving results: {str(e)}" |
| | logger.error(error_msg) |
| | logger.error(traceback.format_exc()) |
| | return None, error_msg |
| |
|
| | def reset_all(self, image): |
| | self.results = [] |
| | self.marks = [] |
| | self.reset_view() |
| | return self.update_display(), "All data has been reset" |
| |
|
| | def load_dicom(self, file): |
| | try: |
| | if file is None: |
| | return None, "No file uploaded" |
| | |
| | if hasattr(file, 'name'): |
| | dicom_data = pydicom.dcmread(file.name) |
| | else: |
| | dicom_data = pydicom.dcmread(file) |
| | |
| | image = dicom_data.pixel_array.astype(np.float32) |
| | self.original_image = image.copy() |
| | |
| | rescale_slope = getattr(dicom_data, 'RescaleSlope', 1) |
| | rescale_intercept = getattr(dicom_data, 'RescaleIntercept', 0) |
| | image = (image * rescale_slope) + rescale_intercept |
| | |
| | self.current_image = image |
| | self.dicom_data = dicom_data |
| | self.display_image = self.normalize_image(image) |
| | self.original_display = self.display_image.copy() |
| | |
| | self.reset_all(None) |
| | print("DICOM file loaded successfully") |
| | |
| | return self.display_image, "DICOM file loaded successfully" |
| | except Exception as e: |
| | print(f"Error loading DICOM file: {str(e)}") |
| | return None, f"Error loading DICOM file: {str(e)}" |
| |
|
| | def normalize_image(self, image): |
| | try: |
| | normalized = cv2.normalize( |
| | image, |
| | None, |
| | alpha=0, |
| | beta=255, |
| | norm_type=cv2.NORM_MINMAX, |
| | dtype=cv2.CV_8U |
| | ) |
| | if len(normalized.shape) == 2: |
| | normalized = cv2.cvtColor(normalized, cv2.COLOR_GRAY2BGR) |
| | return normalized |
| | except Exception as e: |
| | print(f"Error normalizing image: {str(e)}") |
| | return None |
| |
|
| | def reset_view(self): |
| | self.zoom_factor = 1.0 |
| | self.pan_x = 0 |
| | self.pan_y = 0 |
| | if self.original_display is not None: |
| | return self.update_display() |
| | return None |
| |
|
| | def zoom_in(self, image): |
| | print("Zooming in...") |
| | self.zoom_factor = min(20.0, self.zoom_factor + 0.5) |
| | return self.update_display() |
| |
|
| | def zoom_out(self, image): |
| | print("Zooming out...") |
| | self.zoom_factor = max(1.0, self.zoom_factor - 0.5) |
| | return self.update_display() |
| |
|
| | def handle_keyboard(self, key): |
| | try: |
| | print(f"Handling key press: {key}") |
| | pan_amount = int(10 * self.zoom_factor) |
| | |
| | if key == 'ArrowLeft': |
| | self.pan_x = max(0, self.pan_x - pan_amount) |
| | elif key == 'ArrowRight': |
| | self.pan_x = min(self.max_pan_x, self.pan_x + pan_amount) |
| | elif key == 'ArrowUp': |
| | self.pan_y = max(0, self.pan_y - pan_amount) |
| | elif key == 'ArrowDown': |
| | self.pan_y = min(self.max_pan_y, self.pan_y + pan_amount) |
| | |
| | return self.update_display() |
| | except Exception as e: |
| | print(f"Error handling keyboard input: {str(e)}") |
| | return self.display_image |
| |
|
| | def update_display(self): |
| | try: |
| | if self.original_display is None: |
| | return None |
| |
|
| | height, width = self.original_display.shape[:2] |
| | new_height = int(height * self.zoom_factor) |
| | new_width = int(width * self.zoom_factor) |
| |
|
| | zoomed = cv2.resize( |
| | self.original_display, |
| | (new_width, new_height), |
| | interpolation=cv2.INTER_CUBIC |
| | ) |
| |
|
| | zoomed_bgr = cv2.cvtColor(zoomed, cv2.COLOR_RGB2BGR) |
| |
|
| | for x, y, diameter in self.marks: |
| | zoomed_x = int(x * self.zoom_factor) |
| | zoomed_y = int(y * self.zoom_factor) |
| | zoomed_radius = int((diameter / 2.0) * self.zoom_factor) |
| | |
| | |
| | cv2.circle( |
| | zoomed_bgr, |
| | (zoomed_x, zoomed_y), |
| | zoomed_radius, |
| | self.CIRCLE_COLOR, |
| | 1, |
| | lineType=cv2.LINE_AA |
| | ) |
| | |
| | |
| | num_points = 8 |
| | for i in range(num_points): |
| | angle = 2 * np.pi * i / num_points |
| | point_x = int(zoomed_x + zoomed_radius * np.cos(angle)) |
| | point_y = int(zoomed_y + zoomed_radius * np.sin(angle)) |
| | cv2.circle( |
| | zoomed_bgr, |
| | (point_x, point_y), |
| | 1, |
| | self.SMALL_CIRCLES_COLOR, |
| | -1, |
| | lineType=cv2.LINE_AA |
| | ) |
| |
|
| | zoomed = cv2.cvtColor(zoomed_bgr, cv2.COLOR_BGR2RGB) |
| |
|
| | self.max_pan_x = max(0, new_width - width) |
| | self.max_pan_y = max(0, new_height - height) |
| | self.pan_x = min(max(0, self.pan_x), self.max_pan_x) |
| | self.pan_y = min(max(0, self.pan_y), self.max_pan_y) |
| |
|
| | visible = zoomed[ |
| | int(self.pan_y):int(self.pan_y + height), |
| | int(self.pan_x):int(self.pan_x + width) |
| | ] |
| |
|
| | return visible |
| | except Exception as e: |
| | print(f"Error updating display: {str(e)}") |
| | return self.original_display |
| |
|
| | def analyze_roi(self, evt: gr.SelectData): |
| | try: |
| | if self.current_image is None: |
| | return None, "No image loaded" |
| |
|
| | clicked_x = evt.index[0] |
| | clicked_y = evt.index[1] |
| | |
| | x = clicked_x + self.pan_x |
| | y = clicked_y + self.pan_y |
| |
|
| | if self.zoom_factor != 1.0: |
| | x = x / self.zoom_factor |
| | y = y / self.zoom_factor |
| | |
| | x = int(round(x)) |
| | y = int(round(y)) |
| | |
| | height, width = self.original_image.shape[:2] |
| | Y, X = np.ogrid[:height, :width] |
| | radius = self.circle_diameter / 2.0 |
| | r_squared = radius * radius |
| | |
| | dx = X - x |
| | dy = Y - y |
| | dist_squared = dx * dx + dy * dy |
| | |
| | mask = np.zeros((height, width), dtype=bool) |
| | mask[dist_squared <= r_squared] = True |
| | |
| | roi_pixels = self.original_image[mask] |
| | |
| | if len(roi_pixels) == 0: |
| | return self.display_image, "Error: No pixels selected" |
| |
|
| | pixel_spacing = float(self.dicom_data.PixelSpacing[0]) |
| | n_pixels = np.sum(mask) |
| | area = n_pixels * (pixel_spacing ** 2) |
| | |
| | mean_value = np.mean(roi_pixels) |
| | std_dev = np.std(roi_pixels, ddof=1) |
| | min_val = np.min(roi_pixels) |
| | max_val = np.max(roi_pixels) |
| |
|
| | rescale_slope = getattr(self.dicom_data, 'RescaleSlope', 1) |
| | rescale_intercept = getattr(self.dicom_data, 'RescaleIntercept', 0) |
| | |
| | mean_value = (mean_value * rescale_slope) + rescale_intercept |
| | std_dev = std_dev * rescale_slope |
| | min_val = (min_val * rescale_slope) + rescale_intercept |
| | max_val = (max_val * rescale_slope) + rescale_intercept |
| |
|
| | result = { |
| | 'Area (mm²)': f"{area:.3f}", |
| | 'Mean': f"{mean_value:.3f}", |
| | 'StdDev': f"{std_dev:.3f}", |
| | 'Min': f"{min_val:.3f}", |
| | 'Max': f"{max_val:.3f}", |
| | 'Point': f"({x}, {y})" |
| | } |
| | |
| | self.results.append(result) |
| | self.marks.append((x, y, self.circle_diameter)) |
| |
|
| | return self.update_display(), self.format_results() |
| | except Exception as e: |
| | print(f"Error analyzing ROI: {str(e)}") |
| | return self.display_image, f"Error analyzing ROI: {str(e)}" |
| |
|
| | def add_formulas_to_template(self, ws, row_pair, col_group, red_font): |
| | """ |
| | Inserts SNR (first row) and CNR (second row) formulas with IFERROR. |
| | """ |
| | try: |
| | base_col = col_group[1] |
| | std_col = col_group[2] |
| | |
| | row1, row2 = row_pair |
| | |
| | formula_col = get_column_letter(column_index_from_string(col_group[-1]) + 1) |
| | |
| | |
| | formula_snr = f"=IFERROR({base_col}{row1}/{std_col}{row1},\"\")" |
| | cell_snr = ws[f"{formula_col}{row1}"] |
| | cell_snr.value = formula_snr |
| | cell_snr.font = red_font |
| | cell_snr.alignment = openpyxl.styles.Alignment(horizontal='center') |
| | |
| | |
| | formula_cnr = f"=IFERROR(({base_col}{row1}-{base_col}{row2})/{std_col}{row2},\"\")" |
| | cell_cnr = ws[f"{formula_col}{row2}"] |
| | cell_cnr.value = formula_cnr |
| | cell_cnr.font = red_font |
| | cell_cnr.alignment = openpyxl.styles.Alignment(horizontal='center') |
| | |
| | logger.debug(f"Added formulas for rows {row1},{row2} in column {formula_col}") |
| | except Exception as e: |
| | logger.error(f"Error adding formulas: {str(e)}") |
| |
|
| | def save_formatted_results(self, output_path): |
| | try: |
| | if not self.results: |
| | return None, "No results to save" |
| |
|
| | wb = openpyxl.Workbook() |
| | ws = wb.active |
| | red_font = openpyxl.styles.Font(color="FF0000") |
| | center_alignment = openpyxl.styles.Alignment(horizontal='center', vertical='center') |
| | |
| | headers = ['Area', 'Mean', 'StdDev', 'Min', 'Max'] |
| |
|
| | column_groups = [ |
| | ('B', 'C', 'D', 'E', 'F'), ('H', 'I', 'J', 'K', 'L'), |
| | ('N', 'O', 'P', 'Q', 'R'), ('T', 'U', 'V', 'W', 'X'), |
| | ('Z', 'AA', 'AB', 'AC', 'AD'), ('AF', 'AG', 'AH', 'AI', 'AJ'), |
| | ('AL', 'AM', 'AN', 'AO', 'AP'), ('AR', 'AS', 'AT', 'AU', 'AV'), |
| | ('AX', 'AY', 'AZ', 'BA', 'BB'), ('BD', 'BE', 'BF', 'BG', 'BH'), |
| | ('BJ', 'BK', 'BL', 'BM', 'BN'), ('BP', 'BQ', 'BR', 'BS', 'BT'), |
| | ('BV', 'BW', 'BX', 'BY', 'BZ') |
| | ] |
| | |
| | |
| | for cols in column_groups: |
| | for i, header in enumerate(headers): |
| | cell = ws[f"{cols[i]}1"] |
| | cell.value = header |
| | cell.alignment = center_alignment |
| |
|
| | row_pairs = [ |
| | (2, 3), (5, 6), (8, 9), (11, 12), (14, 15), |
| | (17, 18), (20, 21), (23, 24), (26, 27), (29, 30) |
| | ] |
| |
|
| | phantom_sizes = [ |
| | '(7mm)', '(6.5mm)', '(6mm)', '(5.5mm)', '(5mm)', |
| | '(4.5mm)', '(4mm)', '(3.5mm)', '(3mm)', '(2.5mm)' |
| | ] |
| | |
| | |
| | for i, size in enumerate(phantom_sizes): |
| | header_cell = ws.cell(row=row_pairs[i][0]-1, column=1, value=size) |
| | header_cell.font = red_font |
| | header_cell.alignment = center_alignment |
| |
|
| | |
| | result_idx = 0 |
| | current_col_group = 0 |
| | current_row_pair = 0 |
| | |
| | while result_idx < len(self.results): |
| | if current_row_pair >= len(row_pairs): |
| | break |
| | |
| | cols = column_groups[current_col_group] |
| | row1, row2 = row_pairs[current_row_pair] |
| | |
| | if result_idx < len(self.results): |
| | result = self.results[result_idx] |
| | self._write_result_to_cells(ws, result, cols, row1) |
| | result_idx += 1 |
| | |
| | if result_idx < len(self.results): |
| | result = self.results[result_idx] |
| | self._write_result_to_cells(ws, result, cols, row2) |
| | result_idx += 1 |
| | |
| | self.add_formulas_to_template(ws, (row1,row2), cols, red_font) |
| | |
| | current_col_group += 1 |
| | if current_col_group >= len(column_groups): |
| | current_col_group = 0 |
| | current_row_pair += 1 |
| |
|
| | |
| | for cols in column_groups: |
| | for col in cols: |
| | for row in range(2, 31): |
| | cell = ws[f"{col}{row}"] |
| | if cell.value is not None: |
| | cell.alignment = center_alignment |
| |
|
| | |
| | |
| | |
| | start_row = 35 |
| | ws['C35'] = "1-AVG" |
| | ws['C35'].alignment = center_alignment |
| |
|
| | ws.merge_cells('D35:E35') |
| | ws.merge_cells('F35:G35') |
| | ws.merge_cells('H35:I35') |
| |
|
| | headers_avg = { |
| | 'D35': 'AVG MEAN', |
| | 'F35': 'AVG STDDEV', |
| | 'H35': 'AVG CNR' |
| | } |
| | for c_ref, text_val in headers_avg.items(): |
| | ws[c_ref] = text_val |
| | ws[c_ref].font = red_font |
| | ws[c_ref].alignment = center_alignment |
| |
|
| | |
| | phantom_sizes2 = [ |
| | '(7.0mm)', '(6.5mm)', '(6.0mm)', '(5.5mm)', '(5.0mm)', |
| | '(4.5mm)', '(4.0mm)', '(3.5mm)', '(3.0mm)', '(2.5mm)' |
| | ] |
| |
|
| | for i, size_label in enumerate(phantom_sizes2): |
| | row = start_row + i + 1 |
| |
|
| | ws.merge_cells(f'D{row}:E{row}') |
| | ws.merge_cells(f'F{row}:G{row}') |
| | ws.merge_cells(f'H{row}:I{row}') |
| |
|
| | c_cell = ws[f'C{row}'] |
| | c_cell.value = size_label |
| | c_cell.font = red_font |
| | c_cell.alignment = center_alignment |
| |
|
| | if i >= len(row_pairs): |
| | continue |
| | (raw_row1, raw_row2) = row_pairs[i] |
| |
|
| | mean_values = [] |
| | stddev_values = [] |
| | cnr_cells = [] |
| |
|
| | |
| | for group in column_groups: |
| | mean_col = group[1] |
| | std_col = group[2] |
| | |
| | |
| | m1_val = ws[f"{mean_col}{raw_row1}"].value |
| | try: |
| | m1_val = float(m1_val) if m1_val not in [None,''] else None |
| | except: |
| | m1_val = None |
| | |
| | if m1_val == 0: |
| | m1_val = None |
| | |
| | if m1_val is not None: |
| | mean_values.append(m1_val) |
| |
|
| | |
| | s1_val = ws[f"{std_col}{raw_row1}"].value |
| | try: |
| | s1_val = float(s1_val) if s1_val not in [None,''] else None |
| | except: |
| | s1_val = None |
| | if s1_val == 0: |
| | s1_val = None |
| |
|
| | if s1_val is not None: |
| | stddev_values.append(s1_val) |
| |
|
| | |
| | formula_col = get_column_letter(column_index_from_string(group[-1]) + 1) |
| | cnr_cell_ref = f"{formula_col}{raw_row2}" |
| |
|
| | |
| | |
| | mean2_val = ws[f"{mean_col}{raw_row2}"].value |
| | std2_val = ws[f"{std_col}{raw_row2}"].value |
| | try: |
| | mean2_val = float(mean2_val) if mean2_val not in [None,''] else None |
| | std2_val = float(std2_val) if std2_val not in [None,''] else None |
| | except: |
| | mean2_val, std2_val = None, None |
| |
|
| | if mean2_val == 0: |
| | mean2_val = None |
| | if std2_val == 0: |
| | std2_val = None |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if (m1_val is not None) and (mean2_val is not None) and (std2_val is not None): |
| | cnr_cells.append(cnr_cell_ref) |
| |
|
| | |
| | final_mean = sum(mean_values)/len(mean_values) if mean_values else None |
| | if final_mean is not None: |
| | ws[f'D{row}'].value = final_mean |
| | ws[f'D{row}'].alignment = center_alignment |
| | ws[f'D{row}'].number_format = '0.0000' |
| |
|
| | |
| | final_std = sum(stddev_values)/len(stddev_values) if stddev_values else None |
| | if final_std is not None: |
| | ws[f'F{row}'].value = final_std |
| | ws[f'F{row}'].alignment = center_alignment |
| | ws[f'F{row}'].number_format = '0.0000' |
| |
|
| | |
| | if cnr_cells: |
| | formula_avg_cnr = f"=IFERROR(AVERAGE({','.join(cnr_cells)}),\"\")" |
| | ws[f'H{row}'].value = formula_avg_cnr |
| | ws[f'H{row}'].alignment = center_alignment |
| | ws[f'H{row}'].number_format = '0.0000' |
| |
|
| | |
| | thin_side = openpyxl.styles.Side(style='thin') |
| | border = openpyxl.styles.Border( |
| | left=thin_side, right=thin_side, top=thin_side, bottom=thin_side |
| | ) |
| | for r in range(35, 46): |
| | for col in ['C','D','E','F','G','H','I']: |
| | ws[f"{col}{r}"].border = border |
| |
|
| | wb.save(output_path) |
| | return output_path, f"Results saved successfully ({len(self.results)} measurements)" |
| | except Exception as e: |
| | logger.error(f"Error saving formatted results: {str(e)}") |
| | return None, f"Error saving results: {str(e)}" |
| |
|
| | def _write_result_to_cells(self, ws, result, cols, row): |
| | center_alignment = openpyxl.styles.Alignment(horizontal='center') |
| | |
| | value_mapping = { |
| | 'Area': 'Area (mm²)', |
| | 'Mean': 'Mean', |
| | 'StdDev': 'StdDev', |
| | 'Min': 'Min', |
| | 'Max': 'Max' |
| | } |
| | |
| | for i, (header, key) in enumerate(value_mapping.items()): |
| | cell = ws[f"{cols[i]}{row}"] |
| | val = result[key] |
| | cell.value = float(val) if val not in ['', None] else '' |
| | cell.alignment = center_alignment |
| |
|
| | def format_results(self): |
| | if not self.results: |
| | return "No measurements yet" |
| | df = pd.DataFrame(self.results) |
| | columns_order = ['Area (mm²)', 'Mean', 'StdDev', 'Min', 'Max', 'Point'] |
| | df = df[columns_order] |
| | return df.to_string(index=False) |
| |
|
| | def add_zero_row(self, image): |
| | self.results.append({ |
| | 'Area (mm²)': '0.000', |
| | 'Mean': '0.000', |
| | 'StdDev': '0.000', |
| | 'Min': '0.000', |
| | 'Max': '0.000', |
| | 'Point': '(0, 0)' |
| | }) |
| | return image, self.format_results() |
| |
|
| | def add_two_zero_rows(self, image): |
| | for _ in range(2): |
| | self.results.append({ |
| | 'Area (mm²)': '0.000', |
| | 'Mean': '0.000', |
| | 'StdDev': '0.000', |
| | 'Min': '0.000', |
| | 'Max': '0.000', |
| | 'Point': '(0, 0)' |
| | }) |
| | return image, self.format_results() |
| |
|
| | def undo_last(self, image): |
| | if not self.results: |
| | return self.update_display(), self.format_results() |
| | |
| | last_result = self.results[-1] |
| | is_measurement = (last_result['Point'] != '(0, 0)') |
| | self.results.pop() |
| | |
| | if is_measurement and self.marks: |
| | self.marks.pop() |
| | |
| | return self.update_display(), self.format_results() |
| |
|
| |
|
| | def create_interface(): |
| | print("Creating interface...") |
| | analyzer = DicomAnalyzer() |
| | |
| | with gr.Blocks(css="#image_display { outline: none; }") as interface: |
| | gr.Markdown("# DICOM Image Analyzer") |
| | |
| | with gr.Row(): |
| | with gr.Column(): |
| | file_input = gr.File(label="Upload DICOM file") |
| | diameter_slider = gr.Slider( |
| | minimum=1, |
| | maximum=20, |
| | value=9, |
| | step=1, |
| | label="ROI Diameter (pixels)" |
| | ) |
| | |
| | with gr.Row(): |
| | zoom_in_btn = gr.Button("Zoom In (+)") |
| | zoom_out_btn = gr.Button("Zoom Out (-)") |
| | reset_btn = gr.Button("Reset View") |
| | reset_all_btn = gr.Button("Reset All") |
| | |
| | with gr.Column(): |
| | image_display = gr.Image( |
| | label="DICOM Image", |
| | interactive=True, |
| | elem_id="image_display" |
| | ) |
| | |
| | with gr.Row(): |
| | zero_btn = gr.Button("Add Zero Row") |
| | zero2_btn = gr.Button("Add Two Zero Rows") |
| | undo_btn = gr.Button("Undo Last") |
| | save_btn = gr.Button("Save Results") |
| | save_formatted_btn = gr.Button("Save Formatted Results") |
| | |
| | results_display = gr.Textbox(label="Results", interactive=False) |
| | file_output = gr.File(label="Download Results") |
| | key_press = gr.Textbox(visible=False, elem_id="key_press") |
| | |
| | gr.Markdown(""" |
| | ### Controls: |
| | - Use arrow keys to pan when zoomed in. Movement is now larger. |
| | - Click points to measure ROI. |
| | - Use Zoom In/Out buttons or Reset View to adjust zoom level. |
| | - Use Reset All to clear all measurements. |
| | - "Save Results": basic Excel with raw data. |
| | - "Save Formatted Results": Excel with advanced formatting & formulas. |
| | """) |
| |
|
| | def update_diameter(x): |
| | analyzer.circle_diameter = float(x) |
| | print(f"Diameter updated to: {x}") |
| | return f"Diameter set to {x} pixels" |
| |
|
| | def save_formatted(): |
| | output_path = "analysis_results_formatted.xlsx" |
| | return analyzer.save_formatted_results(output_path) |
| |
|
| | file_input.change( |
| | fn=analyzer.load_dicom, |
| | inputs=file_input, |
| | outputs=[image_display, results_display] |
| | ) |
| | |
| | image_display.select( |
| | fn=analyzer.analyze_roi, |
| | outputs=[image_display, results_display] |
| | ) |
| | |
| | diameter_slider.change( |
| | fn=update_diameter, |
| | inputs=diameter_slider, |
| | outputs=gr.Textbox(label="Status") |
| | ) |
| | |
| | zoom_in_btn.click( |
| | fn=analyzer.zoom_in, |
| | inputs=image_display, |
| | outputs=image_display, |
| | queue=False |
| | ) |
| | |
| | zoom_out_btn.click( |
| | fn=analyzer.zoom_out, |
| | inputs=image_display, |
| | outputs=image_display, |
| | queue=False |
| | ) |
| | |
| | reset_btn.click( |
| | fn=analyzer.reset_view, |
| | outputs=image_display |
| | ) |
| | |
| | reset_all_btn.click( |
| | fn=analyzer.reset_all, |
| | inputs=image_display, |
| | outputs=[image_display, results_display] |
| | ) |
| | |
| | key_press.change( |
| | fn=analyzer.handle_keyboard, |
| | inputs=key_press, |
| | outputs=image_display |
| | ) |
| | |
| | zero_btn.click( |
| | fn=analyzer.add_zero_row, |
| | inputs=image_display, |
| | outputs=[image_display, results_display] |
| | ) |
| | |
| | zero2_btn.click( |
| | fn=analyzer.add_two_zero_rows, |
| | inputs=image_display, |
| | outputs=[image_display, results_display] |
| | ) |
| |
|
| | undo_btn.click( |
| | fn=analyzer.undo_last, |
| | inputs=image_display, |
| | outputs=[image_display, results_display] |
| | ) |
| | |
| | save_btn.click( |
| | fn=analyzer.save_results, |
| | outputs=[file_output, results_display] |
| | ) |
| |
|
| | save_formatted_btn.click( |
| | fn=save_formatted, |
| | outputs=[file_output, results_display] |
| | ) |
| |
|
| | |
| | js = """ |
| | <script> |
| | document.addEventListener('keydown', function(e) { |
| | if (['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.key)) { |
| | e.preventDefault(); |
| | const el = document.querySelector('#key_press textarea'); |
| | if (el) { |
| | el.value = e.key; |
| | el.dispatchEvent(new Event('input')); |
| | setTimeout(() => { |
| | el.value = ''; |
| | el.dispatchEvent(new Event('input')); |
| | }, 100); |
| | } |
| | } |
| | }); |
| | </script> |
| | """ |
| | gr.HTML(js) |
| | |
| | print("Interface created successfully") |
| | return interface |
| |
|
| |
|
| | if __name__ == "__main__": |
| | try: |
| | print("Starting application...") |
| | interface = create_interface() |
| | print("Launching interface...") |
| | interface.queue() |
| | interface.launch( |
| | server_name="0.0.0.0", |
| | server_port=7860, |
| | share=True, |
| | debug=True, |
| | show_error=True, |
| | quiet=False |
| | ) |
| | except Exception as e: |
| | print(f"Error launching application: {str(e)}") |
| | logger.error(f"Error launching application: {str(e)}") |
| | logger.error(traceback.format_exc()) |
| | raise e |
| |
|