--- a +++ b/gi-bleeding-detection.py @@ -0,0 +1,337 @@ +import numpy as np +import cv2 +import tkinter as tk +from tkinter import filedialog, messagebox +from PIL import Image, ImageTk +import os +from sklearn.cluster import KMeans +import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg + +class GIBleedingDetector: + def __init__(self): + self.original_image = None + self.processed_image = None + self.mask = None + self.bleeding_percentage = 0 + self.bleeding_status = "No image analyzed" + + def load_image(self, image_path): + """Load and prepare the image for processing""" + self.original_image = cv2.imread(image_path) + if self.original_image is None: + return False + return True + + def process_image(self): + """Process the image to detect GI bleeding""" + if self.original_image is None: + return False + + # Convert to RGB for better color analysis + image_rgb = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2RGB) + + # Resize image for faster processing if needed + resized = cv2.resize(image_rgb, (0, 0), fx=0.5, fy=0.5) if image_rgb.shape[0] > 1000 else image_rgb + + # Reshape the image for K-means + pixels = resized.reshape(-1, 3).astype(np.float32) + + # Define criteria and apply K-means + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) + k = 5 # Number of clusters + _, labels, centers = cv2.kmeans(pixels, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) + + # Convert back to uint8 + centers = np.uint8(centers) + segmented_image = centers[labels.flatten()] + segmented_image = segmented_image.reshape(resized.shape) + + # Detect red regions (potential bleeding) + # Convert to HSV for better color segmentation + hsv_img = cv2.cvtColor(segmented_image, cv2.COLOR_RGB2HSV) + + # Define range for red color in HSV + lower_red1 = np.array([0, 120, 70]) + upper_red1 = np.array([10, 255, 255]) + lower_red2 = np.array([170, 120, 70]) + upper_red2 = np.array([180, 255, 255]) + + # Create masks for red regions + mask1 = cv2.inRange(hsv_img, lower_red1, upper_red1) + mask2 = cv2.inRange(hsv_img, lower_red2, upper_red2) + + # Combine masks + self.mask = cv2.bitwise_or(mask1, mask2) + + # Calculate bleeding percentage + total_pixels = self.mask.size + bleeding_pixels = cv2.countNonZero(self.mask) + self.bleeding_percentage = (bleeding_pixels / total_pixels) * 100 + + # Determine bleeding status + if self.bleeding_percentage > 5: + self.bleeding_status = "High probability of bleeding" + elif self.bleeding_percentage > 1: + self.bleeding_status = "Moderate probability of bleeding" + else: + self.bleeding_status = "Low probability of bleeding" + + # Create visual result + self.processed_image = cv2.bitwise_and(segmented_image, segmented_image, mask=self.mask) + + # Resize the mask to match the original image size if needed + if resized.shape != image_rgb.shape: + self.mask = cv2.resize(self.mask, (image_rgb.shape[1], image_rgb.shape[0])) + self.processed_image = cv2.resize(self.processed_image, (image_rgb.shape[1], image_rgb.shape[0])) + + return True + + def get_overlay_image(self): + """Create an overlay of original image with bleeding highlighted""" + if self.original_image is None or self.mask is None: + return None + + # Convert original image to RGB + original_rgb = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2RGB) + + # Create a mask with same dimensions as original + mask_resized = cv2.resize(self.mask, (original_rgb.shape[1], original_rgb.shape[0])) + + # Create overlay + overlay = original_rgb.copy() + overlay[mask_resized > 0] = [255, 0, 0] # Mark bleeding areas in red + + # Create blended image - 70% original, 30% overlay + blended = cv2.addWeighted(original_rgb, 0.7, overlay, 0.3, 0) + + return blended + + def get_analysis_result(self): + """Return analysis results as a dictionary""" + return { + "bleeding_percentage": round(self.bleeding_percentage, 2), + "bleeding_status": self.bleeding_status + } + +class GIBleedingDetectionApp: + def __init__(self, root): + self.root = root + self.root.title("GI Bleeding Detection Tool") + self.root.geometry("1200x800") + self.root.configure(bg="#f0f0f0") + + self.detector = GIBleedingDetector() + self.create_widgets() + + def create_widgets(self): + # Top frame for buttons + self.top_frame = tk.Frame(self.root, bg="#f0f0f0") + self.top_frame.pack(pady=10, fill=tk.X) + + # Load button + self.load_btn = tk.Button(self.top_frame, text="Load Image", + command=self.load_image, + bg="#4CAF50", fg="white", + font=("Arial", 12), padx=10, pady=5) + self.load_btn.pack(side=tk.LEFT, padx=10) + + # Analyze button + self.analyze_btn = tk.Button(self.top_frame, text="Analyze Image", + command=self.analyze_image, + bg="#2196F3", fg="white", + font=("Arial", 12), padx=10, pady=5, + state=tk.DISABLED) + self.analyze_btn.pack(side=tk.LEFT, padx=10) + + # Save button + self.save_btn = tk.Button(self.top_frame, text="Save Results", + command=self.save_results, + bg="#FFC107", fg="white", + font=("Arial", 12), padx=10, pady=5, + state=tk.DISABLED) + self.save_btn.pack(side=tk.LEFT, padx=10) + + # Main content frame + self.content_frame = tk.Frame(self.root, bg="#f0f0f0") + self.content_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10) + + # Original image frame + self.original_frame = tk.LabelFrame(self.content_frame, text="Original Image", + bg="#f0f0f0", font=("Arial", 12)) + self.original_frame.grid(row=0, column=0, padx=10, pady=10, sticky=tk.NSEW) + + self.original_canvas = tk.Label(self.original_frame, bg="black") + self.original_canvas.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # Processed image frame + self.processed_frame = tk.LabelFrame(self.content_frame, text="Bleeding Detection Result", + bg="#f0f0f0", font=("Arial", 12)) + self.processed_frame.grid(row=0, column=1, padx=10, pady=10, sticky=tk.NSEW) + + self.processed_canvas = tk.Label(self.processed_frame, bg="black") + self.processed_canvas.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # Results frame + self.results_frame = tk.LabelFrame(self.content_frame, text="Analysis Results", + bg="#f0f0f0", font=("Arial", 12)) + self.results_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=10, sticky=tk.NSEW) + + # Results display + self.results_text = tk.Label(self.results_frame, text="No image analyzed", + font=("Arial", 14), bg="#f0f0f0", justify=tk.LEFT) + self.results_text.pack(side=tk.LEFT, padx=20, pady=20) + + # Graph frame for visualization + self.graph_frame = tk.Frame(self.results_frame, bg="#f0f0f0") + self.graph_frame.pack(side=tk.RIGHT, padx=20, pady=20, fill=tk.BOTH, expand=True) + + # Configure the grid + self.content_frame.grid_columnconfigure(0, weight=1) + self.content_frame.grid_columnconfigure(1, weight=1) + self.content_frame.grid_rowconfigure(0, weight=3) + self.content_frame.grid_rowconfigure(1, weight=1) + + # Status bar + self.status_bar = tk.Label(self.root, text="Ready", bd=1, relief=tk.SUNKEN, anchor=tk.W) + self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) + + def load_image(self): + file_path = filedialog.askopenfilename( + title="Select Image", + filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp")]) + + if not file_path: + return + + self.status_bar.config(text=f"Loading image: {file_path}") + + if self.detector.load_image(file_path): + # Convert for display + img = cv2.cvtColor(self.detector.original_image, cv2.COLOR_BGR2RGB) + img = self.resize_for_display(img, 500) + + # Display image + self.tk_img = ImageTk.PhotoImage(Image.fromarray(img)) + self.original_canvas.config(image=self.tk_img) + + # Update UI + self.analyze_btn.config(state=tk.NORMAL) + self.results_text.config(text="Image loaded. Click 'Analyze Image' to detect bleeding.") + self.status_bar.config(text=f"Image loaded: {os.path.basename(file_path)}") + else: + messagebox.showerror("Error", "Could not load the image. Please try again with a different file.") + self.status_bar.config(text="Error loading image") + + def analyze_image(self): + if self.detector.original_image is None: + messagebox.showinfo("Info", "Please load an image first.") + return + + self.status_bar.config(text="Analyzing image for GI bleeding...") + + # Process the image + if self.detector.process_image(): + # Get the overlay image with bleeding highlighted + overlay_img = self.detector.get_overlay_image() + overlay_img = self.resize_for_display(overlay_img, 500) + + # Display processed image + self.processed_tk_img = ImageTk.PhotoImage(Image.fromarray(overlay_img)) + self.processed_canvas.config(image=self.processed_tk_img) + + # Get analysis results + results = self.detector.get_analysis_result() + + # Update results text + result_text = f"Bleeding Detection Analysis:\n\n" + result_text += f"Bleeding Area: {results['bleeding_percentage']}% of image\n" + result_text += f"Assessment: {results['bleeding_status']}" + + self.results_text.config(text=result_text) + + # Create visualization graph + self.create_results_graph(results['bleeding_percentage']) + + # Update UI + self.save_btn.config(state=tk.NORMAL) + self.status_bar.config(text="Analysis complete") + else: + messagebox.showerror("Error", "Could not analyze the image. Please try again.") + self.status_bar.config(text="Error during analysis") + + def create_results_graph(self, bleeding_percentage): + # Clear previous graph + for widget in self.graph_frame.winfo_children(): + widget.destroy() + + # Create figure and axis + fig, ax = plt.subplots(figsize=(4, 3)) + + # Data for the gauge chart + categories = ['Healthy', 'Bleeding'] + values = [100 - bleeding_percentage, bleeding_percentage] + colors = ['#4CAF50', '#F44336'] + + # Create the pie chart + ax.pie(values, labels=categories, colors=colors, autopct='%1.1f%%', + startangle=90, wedgeprops={'edgecolor': 'white', 'linewidth': 1}) + ax.set_title("Tissue Analysis", fontsize=12) + + # Add to tkinter window + canvas = FigureCanvasTkAgg(fig, self.graph_frame) + canvas.draw() + canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + def resize_for_display(self, image, max_dim): + """Resize image while maintaining aspect ratio""" + h, w = image.shape[:2] + if h > w: + new_h = max_dim + new_w = int(w * (max_dim / h)) + else: + new_w = max_dim + new_h = int(h * (max_dim / w)) + + return cv2.resize(image, (new_w, new_h)) + + def save_results(self): + if self.detector.processed_image is None: + messagebox.showinfo("Info", "Please analyze an image first.") + return + + # Ask for directory to save results + save_dir = filedialog.askdirectory(title="Select Directory to Save Results") + + if not save_dir: + return + + try: + # Save the overlay image + overlay_img = self.detector.get_overlay_image() + cv2.imwrite(os.path.join(save_dir, "bleeding_detection_result.jpg"), + cv2.cvtColor(overlay_img, cv2.COLOR_RGB2BGR)) + + # Save the analysis results as text + results = self.detector.get_analysis_result() + with open(os.path.join(save_dir, "analysis_results.txt"), "w") as f: + f.write(f"GI Bleeding Detection Analysis Results\n") + f.write(f"=====================================\n\n") + f.write(f"Bleeding Area: {results['bleeding_percentage']}% of image\n") + f.write(f"Assessment: {results['bleeding_status']}\n") + f.write(f"\nAnalysis performed on {os.path.basename(save_dir)}\n") + + messagebox.showinfo("Success", f"Results saved to {save_dir}") + self.status_bar.config(text=f"Results saved to {save_dir}") + + except Exception as e: + messagebox.showerror("Error", f"Could not save results: {str(e)}") + self.status_bar.config(text="Error saving results") + +if __name__ == "__main__": + # Create main window + root = tk.Tk() + app = GIBleedingDetectionApp(root) + + # Run the application + root.mainloop()