a b/heart_segmentation_gui.py
1
#!/usr/bin/python3
2
3
import os
4
import sys
5
import tkinter as tk
6
from tkinter import filedialog, ttk
7
import SimpleITK as sitk
8
import matplotlib.pyplot as plt
9
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
10
from platipy.imaging.projects.cardiac.run import run_hybrid_segmentation
11
from platipy.imaging import ImageVisualiser
12
from platipy.imaging.label.utils import get_com
13
14
class HeartSegmentationGUI:
15
    def __init__(self, root):
16
        self.root = root
17
        self.root.title("Heart Segmentation Tool")
18
        
19
        # Add protocol handler for window closing
20
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
21
        
22
        # Set minimum window size
23
        self.root.minsize(800, 600)
24
        
25
        # Configure grid weights
26
        self.root.grid_columnconfigure(0, weight=1)
27
        self.root.grid_rowconfigure(0, weight=1)
28
        
29
        # Create main frame with padding
30
        self.main_frame = ttk.Frame(root, padding="20")
31
        self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
32
        self.main_frame.grid_columnconfigure(1, weight=1)
33
        
34
        # Create control panel frame
35
        control_frame = ttk.LabelFrame(self.main_frame, text="Controls", padding="10")
36
        control_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N), padx=5, pady=5)
37
        
38
        # Input file selection
39
        ttk.Button(control_frame, text="Select Input File", width=20, command=self.select_file).grid(
40
            row=0, column=0, padx=(0,10), pady=5)
41
        self.file_label = ttk.Label(control_frame, text="No file selected")
42
        self.file_label.grid(row=0, column=1, sticky=tk.W, pady=5)
43
        
44
        # Output directory selection
45
        ttk.Button(control_frame, text="Select Output Directory", width=20, command=self.select_output_dir).grid(
46
            row=1, column=0, padx=(0,10), pady=5)
47
        self.output_label = ttk.Label(control_frame, text="No directory selected")
48
        self.output_label.grid(row=1, column=1, sticky=tk.W, pady=5)
49
        
50
        # Process button
51
        self.process_button = ttk.Button(control_frame, text="Process", width=20, command=self.process_image)
52
        self.process_button.grid(row=2, column=0, columnspan=2, pady=10)
53
        self.process_button.state(['disabled'])
54
        
55
        # Create status frame
56
        status_frame = ttk.LabelFrame(self.main_frame, text="Status", padding="10")
57
        status_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), padx=5, pady=5)
58
        
59
        # Status label
60
        self.status_label = ttk.Label(status_frame, text="")
61
        self.status_label.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=5, pady=5)
62
        
63
        # Create results frame
64
        results_frame = ttk.LabelFrame(self.main_frame, text="Results", padding="10")
65
        results_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)
66
        results_frame.grid_columnconfigure(0, weight=1)
67
        results_frame.grid_rowconfigure(0, weight=1)
68
        
69
        # Result display
70
        self.figure_frame = ttk.Frame(results_frame)
71
        self.figure_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
72
73
    def select_output_dir(self):
74
        directory = filedialog.askdirectory()
75
        if directory:
76
            self.output_dir = directory
77
            self.output_label.config(text=directory)
78
            self.check_process_ready()
79
            
80
    def select_file(self):
81
        filepath = filedialog.askopenfilename(
82
            filetypes=[("NIFTI files", "*.nii.gz"), ("All files", "*.*")]
83
        )
84
        if filepath:
85
            self.input_file = filepath
86
            self.file_label.config(text=os.path.basename(filepath))
87
            self.check_process_ready()
88
            
89
    def check_process_ready(self):
90
        if hasattr(self, 'input_file') and hasattr(self, 'output_dir'):
91
            self.process_button.state(['!disabled'])
92
        else:
93
            self.process_button.state(['disabled'])
94
            
95
    def process_image(self):
96
        if not hasattr(self, 'input_file') or not hasattr(self, 'output_dir'):
97
            return
98
            
99
        self.status_label.config(text="Processing...")
100
        self.process_button.state(['disabled'])
101
        
102
        self.root.after(100, self.run_segmentation)
103
        
104
    def run_segmentation(self):
105
        try:
106
            # Create output directory
107
            filename = os.path.splitext(os.path.splitext(os.path.basename(self.input_file))[0])[0]
108
            output_dir = os.path.join(self.output_dir, filename)
109
            os.makedirs(output_dir, exist_ok=True)
110
            
111
            # Read image
112
            self.status_label.config(text="Loading image...")
113
            self.root.update()
114
            test_image = sitk.ReadImage(self.input_file)
115
            
116
            # Process image
117
            self.status_label.config(text="Running heart segmentation...")
118
            self.root.update()
119
            auto_structures, _ = run_hybrid_segmentation(test_image)
120
            
121
            # Save segmentations
122
            self.status_label.config(text="Saving segmentation results...")
123
            self.root.update()
124
            for struct_name in auto_structures.keys():
125
                output_path = os.path.join(output_dir, f"{filename}_{struct_name}.nii.gz")
126
                sitk.WriteImage(auto_structures[struct_name], output_path)
127
            
128
            # Create visualization
129
            self.status_label.config(text="Generating visualization...")
130
            self.root.update()
131
            vis = ImageVisualiser(test_image, cut=get_com(auto_structures["Heart"]))
132
            vis.add_contour({struct: auto_structures[struct] for struct in auto_structures.keys()})
133
            fig = vis.show()
134
            
135
            # Adjust figure size
136
            fig.set_size_inches(10, 10)
137
            fig.tight_layout()
138
            
139
            # Save visualization
140
            plt.savefig(os.path.join(output_dir, f"{filename}_heart_visualization.png"))
141
            
142
            # Display in GUI
143
            for widget in self.figure_frame.winfo_children():
144
                widget.destroy()
145
            
146
            canvas = FigureCanvasTkAgg(fig, master=self.figure_frame)
147
            canvas.draw()
148
            canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
149
            
150
            self.status_label.config(text=f"Processing complete. Results saved to {output_dir}")
151
            
152
        except Exception as e:
153
            self.status_label.config(text=f"Error: {str(e)}")
154
        
155
        finally:
156
            self.process_button.state(['!disabled'])
157
158
    def on_closing(self):
159
        self.root.quit()
160
        self.root.destroy()
161
162
if __name__ == "__main__":
163
    root = tk.Tk()
164
    app = HeartSegmentationGUI(root)
165
    root.mainloop()
166
    sys.exit(0)