Diff of /gi-bleeding-detection.py [000000] .. [aa8880]

Switch to unified view

a b/gi-bleeding-detection.py
1
import numpy as np
2
import cv2
3
import tkinter as tk
4
from tkinter import filedialog, messagebox
5
from PIL import Image, ImageTk
6
import os
7
from sklearn.cluster import KMeans
8
import matplotlib.pyplot as plt
9
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
10
11
class GIBleedingDetector:
12
    def __init__(self):
13
        self.original_image = None
14
        self.processed_image = None
15
        self.mask = None
16
        self.bleeding_percentage = 0
17
        self.bleeding_status = "No image analyzed"
18
        
19
    def load_image(self, image_path):
20
        """Load and prepare the image for processing"""
21
        self.original_image = cv2.imread(image_path)
22
        if self.original_image is None:
23
            return False
24
        return True
25
    
26
    def process_image(self):
27
        """Process the image to detect GI bleeding"""
28
        if self.original_image is None:
29
            return False
30
        
31
        # Convert to RGB for better color analysis
32
        image_rgb = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2RGB)
33
        
34
        # Resize image for faster processing if needed
35
        resized = cv2.resize(image_rgb, (0, 0), fx=0.5, fy=0.5) if image_rgb.shape[0] > 1000 else image_rgb
36
        
37
        # Reshape the image for K-means
38
        pixels = resized.reshape(-1, 3).astype(np.float32)
39
        
40
        # Define criteria and apply K-means
41
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
42
        k = 5  # Number of clusters
43
        _, labels, centers = cv2.kmeans(pixels, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
44
        
45
        # Convert back to uint8
46
        centers = np.uint8(centers)
47
        segmented_image = centers[labels.flatten()]
48
        segmented_image = segmented_image.reshape(resized.shape)
49
        
50
        # Detect red regions (potential bleeding)
51
        # Convert to HSV for better color segmentation
52
        hsv_img = cv2.cvtColor(segmented_image, cv2.COLOR_RGB2HSV)
53
        
54
        # Define range for red color in HSV
55
        lower_red1 = np.array([0, 120, 70])
56
        upper_red1 = np.array([10, 255, 255])
57
        lower_red2 = np.array([170, 120, 70])
58
        upper_red2 = np.array([180, 255, 255])
59
        
60
        # Create masks for red regions
61
        mask1 = cv2.inRange(hsv_img, lower_red1, upper_red1)
62
        mask2 = cv2.inRange(hsv_img, lower_red2, upper_red2)
63
        
64
        # Combine masks
65
        self.mask = cv2.bitwise_or(mask1, mask2)
66
        
67
        # Calculate bleeding percentage
68
        total_pixels = self.mask.size
69
        bleeding_pixels = cv2.countNonZero(self.mask)
70
        self.bleeding_percentage = (bleeding_pixels / total_pixels) * 100
71
        
72
        # Determine bleeding status
73
        if self.bleeding_percentage > 5:
74
            self.bleeding_status = "High probability of bleeding"
75
        elif self.bleeding_percentage > 1:
76
            self.bleeding_status = "Moderate probability of bleeding"
77
        else:
78
            self.bleeding_status = "Low probability of bleeding"
79
        
80
        # Create visual result
81
        self.processed_image = cv2.bitwise_and(segmented_image, segmented_image, mask=self.mask)
82
        
83
        # Resize the mask to match the original image size if needed
84
        if resized.shape != image_rgb.shape:
85
            self.mask = cv2.resize(self.mask, (image_rgb.shape[1], image_rgb.shape[0]))
86
            self.processed_image = cv2.resize(self.processed_image, (image_rgb.shape[1], image_rgb.shape[0]))
87
            
88
        return True
89
    
90
    def get_overlay_image(self):
91
        """Create an overlay of original image with bleeding highlighted"""
92
        if self.original_image is None or self.mask is None:
93
            return None
94
            
95
        # Convert original image to RGB
96
        original_rgb = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2RGB)
97
        
98
        # Create a mask with same dimensions as original
99
        mask_resized = cv2.resize(self.mask, (original_rgb.shape[1], original_rgb.shape[0]))
100
        
101
        # Create overlay
102
        overlay = original_rgb.copy()
103
        overlay[mask_resized > 0] = [255, 0, 0]  # Mark bleeding areas in red
104
        
105
        # Create blended image - 70% original, 30% overlay
106
        blended = cv2.addWeighted(original_rgb, 0.7, overlay, 0.3, 0)
107
        
108
        return blended
109
    
110
    def get_analysis_result(self):
111
        """Return analysis results as a dictionary"""
112
        return {
113
            "bleeding_percentage": round(self.bleeding_percentage, 2),
114
            "bleeding_status": self.bleeding_status
115
        }
116
117
class GIBleedingDetectionApp:
118
    def __init__(self, root):
119
        self.root = root
120
        self.root.title("GI Bleeding Detection Tool")
121
        self.root.geometry("1200x800")
122
        self.root.configure(bg="#f0f0f0")
123
        
124
        self.detector = GIBleedingDetector()
125
        self.create_widgets()
126
        
127
    def create_widgets(self):
128
        # Top frame for buttons
129
        self.top_frame = tk.Frame(self.root, bg="#f0f0f0")
130
        self.top_frame.pack(pady=10, fill=tk.X)
131
        
132
        # Load button
133
        self.load_btn = tk.Button(self.top_frame, text="Load Image", 
134
                                 command=self.load_image, 
135
                                 bg="#4CAF50", fg="white",
136
                                 font=("Arial", 12), padx=10, pady=5)
137
        self.load_btn.pack(side=tk.LEFT, padx=10)
138
        
139
        # Analyze button
140
        self.analyze_btn = tk.Button(self.top_frame, text="Analyze Image", 
141
                                    command=self.analyze_image,
142
                                    bg="#2196F3", fg="white",
143
                                    font=("Arial", 12), padx=10, pady=5,
144
                                    state=tk.DISABLED)
145
        self.analyze_btn.pack(side=tk.LEFT, padx=10)
146
        
147
        # Save button
148
        self.save_btn = tk.Button(self.top_frame, text="Save Results", 
149
                                 command=self.save_results,
150
                                 bg="#FFC107", fg="white",
151
                                 font=("Arial", 12), padx=10, pady=5,
152
                                 state=tk.DISABLED)
153
        self.save_btn.pack(side=tk.LEFT, padx=10)
154
        
155
        # Main content frame
156
        self.content_frame = tk.Frame(self.root, bg="#f0f0f0")
157
        self.content_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
158
        
159
        # Original image frame
160
        self.original_frame = tk.LabelFrame(self.content_frame, text="Original Image", 
161
                                           bg="#f0f0f0", font=("Arial", 12))
162
        self.original_frame.grid(row=0, column=0, padx=10, pady=10, sticky=tk.NSEW)
163
        
164
        self.original_canvas = tk.Label(self.original_frame, bg="black")
165
        self.original_canvas.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
166
        
167
        # Processed image frame
168
        self.processed_frame = tk.LabelFrame(self.content_frame, text="Bleeding Detection Result", 
169
                                            bg="#f0f0f0", font=("Arial", 12))
170
        self.processed_frame.grid(row=0, column=1, padx=10, pady=10, sticky=tk.NSEW)
171
        
172
        self.processed_canvas = tk.Label(self.processed_frame, bg="black")
173
        self.processed_canvas.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
174
        
175
        # Results frame
176
        self.results_frame = tk.LabelFrame(self.content_frame, text="Analysis Results", 
177
                                          bg="#f0f0f0", font=("Arial", 12))
178
        self.results_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=10, sticky=tk.NSEW)
179
        
180
        # Results display
181
        self.results_text = tk.Label(self.results_frame, text="No image analyzed", 
182
                                    font=("Arial", 14), bg="#f0f0f0", justify=tk.LEFT)
183
        self.results_text.pack(side=tk.LEFT, padx=20, pady=20)
184
        
185
        # Graph frame for visualization
186
        self.graph_frame = tk.Frame(self.results_frame, bg="#f0f0f0")
187
        self.graph_frame.pack(side=tk.RIGHT, padx=20, pady=20, fill=tk.BOTH, expand=True)
188
        
189
        # Configure the grid
190
        self.content_frame.grid_columnconfigure(0, weight=1)
191
        self.content_frame.grid_columnconfigure(1, weight=1)
192
        self.content_frame.grid_rowconfigure(0, weight=3)
193
        self.content_frame.grid_rowconfigure(1, weight=1)
194
        
195
        # Status bar
196
        self.status_bar = tk.Label(self.root, text="Ready", bd=1, relief=tk.SUNKEN, anchor=tk.W)
197
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
198
        
199
    def load_image(self):
200
        file_path = filedialog.askopenfilename(
201
            title="Select Image", 
202
            filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp")])
203
        
204
        if not file_path:
205
            return
206
            
207
        self.status_bar.config(text=f"Loading image: {file_path}")
208
        
209
        if self.detector.load_image(file_path):
210
            # Convert for display
211
            img = cv2.cvtColor(self.detector.original_image, cv2.COLOR_BGR2RGB)
212
            img = self.resize_for_display(img, 500)
213
            
214
            # Display image
215
            self.tk_img = ImageTk.PhotoImage(Image.fromarray(img))
216
            self.original_canvas.config(image=self.tk_img)
217
            
218
            # Update UI
219
            self.analyze_btn.config(state=tk.NORMAL)
220
            self.results_text.config(text="Image loaded. Click 'Analyze Image' to detect bleeding.")
221
            self.status_bar.config(text=f"Image loaded: {os.path.basename(file_path)}")
222
        else:
223
            messagebox.showerror("Error", "Could not load the image. Please try again with a different file.")
224
            self.status_bar.config(text="Error loading image")
225
    
226
    def analyze_image(self):
227
        if self.detector.original_image is None:
228
            messagebox.showinfo("Info", "Please load an image first.")
229
            return
230
            
231
        self.status_bar.config(text="Analyzing image for GI bleeding...")
232
        
233
        # Process the image
234
        if self.detector.process_image():
235
            # Get the overlay image with bleeding highlighted
236
            overlay_img = self.detector.get_overlay_image()
237
            overlay_img = self.resize_for_display(overlay_img, 500)
238
            
239
            # Display processed image
240
            self.processed_tk_img = ImageTk.PhotoImage(Image.fromarray(overlay_img))
241
            self.processed_canvas.config(image=self.processed_tk_img)
242
            
243
            # Get analysis results
244
            results = self.detector.get_analysis_result()
245
            
246
            # Update results text
247
            result_text = f"Bleeding Detection Analysis:\n\n"
248
            result_text += f"Bleeding Area: {results['bleeding_percentage']}% of image\n"
249
            result_text += f"Assessment: {results['bleeding_status']}"
250
            
251
            self.results_text.config(text=result_text)
252
            
253
            # Create visualization graph
254
            self.create_results_graph(results['bleeding_percentage'])
255
            
256
            # Update UI
257
            self.save_btn.config(state=tk.NORMAL)
258
            self.status_bar.config(text="Analysis complete")
259
        else:
260
            messagebox.showerror("Error", "Could not analyze the image. Please try again.")
261
            self.status_bar.config(text="Error during analysis")
262
    
263
    def create_results_graph(self, bleeding_percentage):
264
        # Clear previous graph
265
        for widget in self.graph_frame.winfo_children():
266
            widget.destroy()
267
            
268
        # Create figure and axis
269
        fig, ax = plt.subplots(figsize=(4, 3))
270
        
271
        # Data for the gauge chart
272
        categories = ['Healthy', 'Bleeding']
273
        values = [100 - bleeding_percentage, bleeding_percentage]
274
        colors = ['#4CAF50', '#F44336']
275
        
276
        # Create the pie chart
277
        ax.pie(values, labels=categories, colors=colors, autopct='%1.1f%%', 
278
               startangle=90, wedgeprops={'edgecolor': 'white', 'linewidth': 1})
279
        ax.set_title("Tissue Analysis", fontsize=12)
280
        
281
        # Add to tkinter window
282
        canvas = FigureCanvasTkAgg(fig, self.graph_frame)
283
        canvas.draw()
284
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
285
    
286
    def resize_for_display(self, image, max_dim):
287
        """Resize image while maintaining aspect ratio"""
288
        h, w = image.shape[:2]
289
        if h > w:
290
            new_h = max_dim
291
            new_w = int(w * (max_dim / h))
292
        else:
293
            new_w = max_dim
294
            new_h = int(h * (max_dim / w))
295
        
296
        return cv2.resize(image, (new_w, new_h))
297
    
298
    def save_results(self):
299
        if self.detector.processed_image is None:
300
            messagebox.showinfo("Info", "Please analyze an image first.")
301
            return
302
            
303
        # Ask for directory to save results
304
        save_dir = filedialog.askdirectory(title="Select Directory to Save Results")
305
        
306
        if not save_dir:
307
            return
308
            
309
        try:
310
            # Save the overlay image
311
            overlay_img = self.detector.get_overlay_image()
312
            cv2.imwrite(os.path.join(save_dir, "bleeding_detection_result.jpg"), 
313
                       cv2.cvtColor(overlay_img, cv2.COLOR_RGB2BGR))
314
            
315
            # Save the analysis results as text
316
            results = self.detector.get_analysis_result()
317
            with open(os.path.join(save_dir, "analysis_results.txt"), "w") as f:
318
                f.write(f"GI Bleeding Detection Analysis Results\n")
319
                f.write(f"=====================================\n\n")
320
                f.write(f"Bleeding Area: {results['bleeding_percentage']}% of image\n")
321
                f.write(f"Assessment: {results['bleeding_status']}\n")
322
                f.write(f"\nAnalysis performed on {os.path.basename(save_dir)}\n")
323
                
324
            messagebox.showinfo("Success", f"Results saved to {save_dir}")
325
            self.status_bar.config(text=f"Results saved to {save_dir}")
326
            
327
        except Exception as e:
328
            messagebox.showerror("Error", f"Could not save results: {str(e)}")
329
            self.status_bar.config(text="Error saving results")
330
331
if __name__ == "__main__":
332
    # Create main window
333
    root = tk.Tk()
334
    app = GIBleedingDetectionApp(root)
335
    
336
    # Run the application
337
    root.mainloop()