|
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) |