import os
import cv2
import numpy as np
import json
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, simpledialog
from PIL import Image, ImageTk
import threading
import logging
# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class VertebraeCropper:
def __init__(self):
self.root = tk.Tk()
self.root.title("腰椎MRI图像裁剪工具 - 专业版")
self.root.geometry("1200x800")
# Initialize variables
self.divisions = [i/8 for i in range(1, 8)]
self.presets = {}
self.current_preset = "default"
self.upper_cut_line = 2
self.lower_cut_line = 5
self.split_line = 3
self.current_image = None
self.current_image_path = None
# Load presets and create UI
self.load_presets()
self.create_widgets()
def create_widgets(self):
"""Create the main GUI widgets"""
# Main frame
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Title
title_label = tk.Label(main_frame, text="腰椎MRI图像批量裁剪工具 - 专业版",
font=("Arial", 16, "bold"))
title_label.pack(pady=10)
# Description
desc_text = """
本工具用于批量处理腰椎MRI图像,将每张图像中的椎体分成两组进行裁剪:
- T12-L2 (上3个椎体)
- L3-L5 (下3个椎体)
您可以在预览界面中调整分割线位置,确保椎体正确分组。
裁剪后的图像会放大并铺满512×512的画布。
"""
desc_label = tk.Label(main_frame, text=desc_text, justify=tk.LEFT, wraplength=1000)
desc_label.pack(pady=5)
# Control frame
control_frame = tk.Frame(main_frame)
control_frame.pack(pady=10, fill=tk.X)
# Preset management
self.create_preset_controls(control_frame)
# Input/Output folder selection
self.create_io_controls(control_frame)
# Buttons
button_frame = tk.Frame(control_frame)
button_frame.pack(pady=10)
self.preview_btn = tk.Button(button_frame, text="预览并调整分割",
command=self.preview_and_adjust, width=20, height=2)
self.preview_btn.pack(side=tk.LEFT, padx=10)
self.process_btn = tk.Button(button_frame, text="开始批量处理",
command=self.start_processing, width=20, height=2)
self.process_btn.pack(side=tk.LEFT, padx=10)
# Progress bar
self.create_progress_bar(main_frame)
# Status bar
self.status_var = tk.StringVar()
self.status_var.set("准备就绪")
status_bar = tk.Label(main_frame, textvariable=self.status_var, bd=1,
relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def create_preset_controls(self, parent):
"""Create preset management controls"""
preset_frame = tk.Frame(parent)
preset_frame.pack(pady=5, fill=tk.X)
tk.Label(preset_frame, text="预设:", width=8, anchor='w').pack(side=tk.LEFT)
self.preset_var = tk.StringVar(value=self.current_preset)
preset_combo = ttk.Combobox(preset_frame, textvariable=self.preset_var, width=15)
preset_combo['values'] = list(self.presets.keys())
preset_combo.pack(side=tk.LEFT, padx=5)
preset_combo.bind('<<ComboboxSelected>>', self.load_selected_preset)
tk.Button(preset_frame, text="保存预设", command=self.save_preset).pack(side=tk.LEFT, padx=5)
tk.Button(preset_frame, text="新建预设", command=self.create_new_preset).pack(side=tk.LEFT, padx=5)
tk.Button(preset_frame, text="删除预设", command=self.delete_preset).pack(side=tk.LEFT, padx=5)
def create_io_controls(self, parent):
"""Create input/output folder controls"""
# Input folder
input_frame = tk.Frame(parent)
input_frame.pack(pady=5, fill=tk.X)
tk.Label(input_frame, text="输入文件夹:", width=12, anchor='w').pack(side=tk.LEFT)
self.input_path = tk.StringVar()
tk.Entry(input_frame, textvariable=self.input_path, width=60).pack(side=tk.LEFT, padx=5)
tk.Button(input_frame, text="浏览", command=self.select_input_folder).pack(side=tk.LEFT)
# Output folder
output_frame = tk.Frame(parent)
output_frame.pack(pady=5, fill=tk.X)
tk.Label(output_frame, text="输出文件夹:", width=12, anchor='w').pack(side=tk.LEFT)
self.output_path = tk.StringVar()
tk.Entry(output_frame, textvariable=self.output_path, width=60).pack(side=tk.LEFT, padx=5)
tk.Button(output_frame, text="浏览", command=self.select_output_folder).pack(side=tk.LEFT)
def create_progress_bar(self, parent):
"""Create progress bar"""
progress_frame = tk.Frame(parent)
progress_frame.pack(pady=10, fill=tk.X)
self.progress = ttk.Progressbar(progress_frame, orient=tk.HORIZONTAL,
length=800, mode='determinate')
self.progress.pack(fill=tk.X)
self.progress_label = tk.Label(progress_frame, text="准备就绪")
self.progress_label.pack(pady=5)
def load_presets(self):
"""Load presets from file"""
try:
if os.path.exists("presets.json"):
with open("presets.json", "r") as f:
data = json.load(f)
self.presets = data.get("presets", {"default": [i/8 for i in range(1, 8)]})
# Load cut line settings
self.upper_cut_line = data.get("upper_cut_line", 2)
self.lower_cut_line = data.get("lower_cut_line", 5)
self.split_line = data.get("split_line", 3)
except Exception as e:
logging.error(f"Error loading presets: {e}")
self.presets = {"default": [i/8 for i in range(1, 8)]}
def save_presets(self):
"""Save presets to file"""
try:
data = {
"presets": self.presets,
"upper_cut_line": self.upper_cut_line,
"lower_cut_line": self.lower_cut_line,
"split_line": self.split_line
}
with open("presets.json", "w") as f:
json.dump(data, f)
except Exception as e:
logging.error(f"Error saving presets: {e}")
messagebox.showerror("错误", f"保存预设时出错: {e}")
def load_selected_preset(self, event=None):
"""Load the selected preset"""
preset_name = self.preset_var.get()
if preset_name in self.presets:
self.divisions = self.presets[preset_name]
self.current_preset = preset_name
def save_preset(self):
"""Save current preset"""
preset_name = self.preset_var.get()
self.presets[preset_name] = self.divisions
self.save_presets()
messagebox.showinfo("成功", f"预设 '{preset_name}' 已保存")
def create_new_preset(self):
"""Create a new preset"""
preset_name = simpledialog.askstring("新建预设", "请输入预设名称:")
if preset_name:
if preset_name in self.presets:
messagebox.showerror("错误", "预设名称已存在")
else:
self.presets[preset_name] = self.divisions
self.preset_var.set(preset_name)
self.current_preset = preset_name
self.save_presets()
messagebox.showinfo("成功", f"预设 '{preset_name}' 已创建")
def delete_preset(self):
"""Delete current preset"""
preset_name = self.preset_var.get()
if preset_name == "default":
messagebox.showerror("错误", "不能删除默认预设")
return
if messagebox.askyesno("确认", f"确定要删除预设 '{preset_name}' 吗?"):
del self.presets[preset_name]
self.preset_var.set("default")
self.current_preset = "default"
self.divisions = self.presets["default"]
self.save_presets()
messagebox.showinfo("成功", f"预设 '{preset_name}' 已删除")
def select_input_folder(self):
"""Select input folder"""
folder = filedialog.askdirectory(title="选择包含MRI图像的文件夹")
if folder:
self.input_path.set(folder)
def select_output_folder(self):
"""Select output folder"""
folder = filedialog.askdirectory(title="选择保存结果的文件夹")
if folder:
self.output_path.set(folder)
def detect_vertebrae_centers(self, image):
"""Detect vertebrae centers in the image"""
try:
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply Gaussian blur to reduce noise
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Use adaptive thresholding
thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2)
# Morphological operations to remove small noise
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# Find contours
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Filter contours that might be vertebrae
vertebra_centers = []
for contour in contours:
area = cv2.contourArea(contour)
if area < 500 or area > 10000: # Filter too small or large areas
continue
# Calculate bounding rectangle
x, y, w, h = cv2.boundingRect(contour)
aspect_ratio = w / h
# Filter by aspect ratio
if 0.5 < aspect_ratio < 2.0:
# Calculate contour center
M = cv2.moments(contour)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
vertebra_centers.append((cX, cY, w, h))
# Sort by y-coordinate (top to bottom)
vertebra_centers.sort(key=lambda center: center[1])
return vertebra_centers
except Exception as e:
logging.error(f"Error detecting vertebrae: {e}")
return []
def preview_and_adjust(self):
"""Preview image and allow adjustment of division lines"""
input_dir = self.input_path.get()
if not input_dir:
messagebox.showerror("错误", "请先选择输入文件夹")
return
# Get first image file
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif')
image_files = [f for f in os.listdir(input_dir)
if f.lower().endswith(image_extensions)]
if not image_files:
messagebox.showerror("错误", "输入文件夹中没有找到图像文件")
return
# Load first image
self.current_image_path = os.path.join(input_dir, image_files[0])
try:
self.current_image = cv2.imread(self.current_image_path)
if self.current_image is None:
raise ValueError("无法读取图像文件")
# Resize to 512x512 if not already
if self.current_image.shape[0] != 512 or self.current_image.shape[1] != 512:
self.current_image = cv2.resize(self.current_image, (512, 512))
# Create interactive adjustment UI
self.create_adjustment_ui()
except Exception as e:
logging.error(f"Error loading image: {e}")
messagebox.showerror("错误", f"无法读取图像文件: {e}")
def create_adjustment_ui(self):
"""Create the adjustment UI"""
# Create new window
self.adjust_window = tk.Toplevel(self.root)
self.adjust_window.title("调整分割线")
self.adjust_window.geometry("1200x800")
# Create matplotlib figure
fig = Figure(figsize=(12, 8), dpi=100)
self.canvas = FigureCanvasTkAgg(fig, master=self.adjust_window)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Add control buttons
self.create_adjustment_controls()
# Create subplot
ax = fig.add_subplot(111)
ax.imshow(cv2.cvtColor(self.current_image, cv2.COLOR_BGR2RGB))
ax.set_title('调整分割线位置 (拖拽红线调整,点击按钮添加/删除分割线)')
# Draw initial division lines
self.draw_division_lines(ax)
# Set up interaction
self.setup_interaction(fig)
fig.tight_layout()
self.canvas.draw()
def create_adjustment_controls(self):
"""Create controls for the adjustment UI"""
# Button frame
button_frame = tk.Frame(self.adjust_window)
button_frame.pack(fill=tk.X, pady=5)
tk.Button(button_frame, text="应用分割", command=self.confirm_divisions).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="保存设置", command=self.save_settings).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="重置分割", command=self.reset_divisions).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="自动检测", command=self.auto_detect).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="添加分割线", command=self.add_division).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="删除分割线", command=self.remove_division).pack(side=tk.LEFT, padx=10)
# Cut line selection frame
cut_frame = tk.Frame(self.adjust_window)
cut_frame.pack(fill=tk.X, pady=5)
tk.Label(cut_frame, text="上切割线:", width=10).pack(side=tk.LEFT)
self.upper_cut_var = tk.IntVar(value=self.upper_cut_line)
upper_spin = tk.Spinbox(cut_frame, from_=0, to=len(self.divisions)-1,
textvariable=self.upper_cut_var, width=5)
upper_spin.pack(side=tk.LEFT, padx=5)
tk.Label(cut_frame, text="下切割线:", width=10).pack(side=tk.LEFT)
self.lower_cut_var = tk.IntVar(value=self.lower_cut_line)
lower_spin = tk.Spinbox(cut_frame, from_=0, to=len(self.divisions)-1,
textvariable=self.lower_cut_var, width=5)
lower_spin.pack(side=tk.LEFT, padx=5)
tk.Label(cut_frame, text="分组线:", width=10).pack(side=tk.LEFT)
self.split_var = tk.IntVar(value=self.split_line)
split_spin = tk.Spinbox(cut_frame, from_=0, to=len(self.divisions)-1,
textvariable=self.split_var, width=5)
split_spin.pack(side=tk.LEFT, padx=5)
def draw_division_lines(self, ax):
"""Draw division lines on the plot"""
self.division_lines = []
height = self.current_image.shape[0]
for i, ratio in enumerate(self.divisions):
y_pos = int(height * ratio)
color = 'red'
if i == self.upper_cut_line:
color = 'green'
elif i == self.lower_cut_line:
color = 'blue'
elif i == self.split_line:
color = 'yellow'
line = ax.axhline(y=y_pos, color=color, linestyle='--', linewidth=2, picker=5)
self.division_lines.append(line)
# Add division line label
ax.text(10, y_pos + 5, f"线 {i+1}", color=color,
bbox=dict(facecolor='white', alpha=0.7))
# Draw vertebra group labels
ax.text(10, height * 0.25, "T12-L2", color='green', fontsize=14,
bbox=dict(facecolor='white', alpha=0.7))
ax.text(10, height * 0.75, "L3-L5", color='blue', fontsize=14,
bbox=dict(facecolor='white', alpha=0.7))
def setup_interaction(self, fig):
"""Set up interaction handlers"""
self.current_dragging_line = None
self.canvas.mpl_connect('pick_event', self.on_pick)
self.canvas.mpl_connect('motion_notify_event', self.on_motion)
self.canvas.mpl_connect('button_release_event', self.on_release)
def on_pick(self, event):
"""Handle line selection"""
if event.artist in self.division_lines:
self.current_dragging_line = event.artist
def on_motion(self, event):
"""Handle line dragging"""
if self.current_dragging_line is not None and event.ydata is not None:
# Constrain line to image bounds
y_pos = max(0, min(event.ydata, self.current_image.shape[0]))
self.current_dragging_line.set_ydata([y_pos, y_pos])
self.canvas.draw()
def on_release(self, event):
"""Handle mouse release"""
self.current_dragging_line = None
def add_division(self):
"""Add a division line"""
# Add new division line at the middle
height = self.current_image.shape[0]
new_y = height // 2
ax = self.canvas.figure.axes[0]
line = ax.axhline(y=new_y, color='red', linestyle='--', linewidth=2, picker=5)
self.division_lines.append(line)
# Add label
ax.text(10, new_y + 5, f"线 {len(self.division_lines)}", color='red',
bbox=dict(facecolor='white', alpha=0.7))
# Update spinbox ranges
self.update_spinbox_ranges()
self.canvas.draw()
def remove_division(self):
"""Remove the last division line"""
if len(self.division_lines) > 1: # Keep at least one division line
line = self.division_lines.pop()
line.remove()
# Update spinbox ranges
self.update_spinbox_ranges()
self.canvas.draw()
else:
messagebox.showwarning("警告", "至少需要保留一条分割线")
def update_spinbox_ranges(self):
"""Update spinbox ranges based on current division lines"""
max_line = len(self.division_lines) - 1
for widget in self.adjust_window.winfo_children():
if isinstance(widget, tk.Frame):
for child in widget.winfo_children():
if isinstance(child, tk.Spinbox):
child.configure(to=max_line)
def save_settings(self):
"""Save current division line settings"""
# Update cut line settings
self.upper_cut_line = self.upper_cut_var.get()
self.lower_cut_line = self.lower_cut_var.get()
self.split_line = self.split_var.get()
# Save to file
self.save_presets()
messagebox.showinfo("成功", "分割线设置已保存")
def confirm_divisions(self):
"""Confirm current division line positions"""
# Update division positions (normalized)
height = self.current_image.shape[0]
self.divisions = [line.get_ydata()[0] / height for line in self.division_lines]
# Update cut line settings
self.upper_cut_line = self.upper_cut_var.get()
self.lower_cut_line = self.lower_cut_var.get()
self.split_line = self.split_var.get()
# Sort divisions
self.divisions.sort()
# Close adjustment window
self.adjust_window.destroy()
# Show preview result
self.show_preview_result()
def reset_divisions(self):
"""Reset division lines to initial positions"""
height = self.current_image.shape[0]
# Reset to equal divisions
self.divisions = [i/8 for i in range(1, 8)]
# Clear all division lines
for line in self.division_lines:
line.remove()
self.division_lines = []
# Redraw division lines
ax = self.canvas.figure.axes[0]
for i, ratio in enumerate(self.divisions):
y_pos = int(height * ratio)
color = 'red'
if i == self.upper_cut_line:
color = 'green'
elif i == self.lower_cut_line:
color = 'blue'
elif i == self.split_line:
color = 'yellow'
line = ax.axhline(y=y_pos, color=color, linestyle='--', linewidth=2, picker=5)
self.division_lines.append(line)
# Add division line label
ax.text(10, y_pos + 5, f"线 {i+1}", color=color,
bbox=dict(facecolor='white', alpha=0.7))
self.canvas.draw()
def auto_detect(self):
"""Automatically detect vertebrae and set division lines"""
vertebra_centers = self.detect_vertebrae_centers(self.current_image)
if len(vertebra_centers) >= 6:
# Get y-coordinates of vertebrae
y_positions = [center[1] for center in vertebra_centers[:6]]
# Calculate division positions (between vertebrae)
new_divisions = []
for i in range(5):
division = (y_positions[i] + y_positions[i+1]) / 2
new_divisions.append(division / self.current_image.shape[0])
# Add extra divisions to maintain count
while len(new_divisions) < 7: # Ensure 7 division lines
new_divisions.append(new_divisions[-1] + 0.05)
# Update division lines
height = self.current_image.shape[0]
# Clear all division lines
for line in self.division_lines:
line.remove()
self.division_lines = []
# Redraw division lines
ax = self.canvas.figure.axes[0]
for i, ratio in enumerate(new_divisions):
y_pos = int(height * ratio)
color = 'red'
if i == self.upper_cut_line:
color = 'green'
elif i == self.lower_cut_line:
color = 'blue'
elif i == self.split_line:
color = 'yellow'
line = ax.axhline(y=y_pos, color=color, linestyle='--', linewidth=2, picker=5)
self.division_lines.append(line)
# Add division line label
ax.text(10, y_pos + 5, f"线 {i+1}", color=color,
bbox=dict(facecolor='white', alpha=0.7))
self.canvas.draw()
else:
messagebox.showwarning("警告", f"只检测到 {len(vertebra_centers)} 个椎体,无法自动分割")
def show_preview_result(self):
"""Show preview of cropping result"""
# Create new window to display cropping results
preview_window = tk.Toplevel(self.root)
preview_window.title("预览裁剪结果")
preview_window.geometry("1200x600")
# Crop image
group1, group2 = self.crop_vertebrae_groups(self.current_image)
# Display results
result_frame = tk.Frame(preview_window)
result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Display T12-L2 group
tk.Label(result_frame, text="T12-L2", font=("Arial", 12, "bold")).grid(row=0, column=0, pady=5)
group1_img = Image.fromarray(cv2.cvtColor(group1, cv2.COLOR_BGR2RGB))
group1_img = group1_img.resize((450, 450), Image.LANCZOS)
group1_tk = ImageTk.PhotoImage(group1_img)
group1_label = tk.Label(result_frame, image=group1_tk)
group1_label.image = group1_tk
group1_label.grid(row=1, column=0, padx=10)
# Display L3-L5 group
tk.Label(result_frame, text="L3-L5", font=("Arial", 12, "bold")).grid(row=0, column=1, pady=5)
group2_img = Image.fromarray(cv2.cvtColor(group2, cv2.COLOR_BGR2RGB))
group2_img = group2_img.resize((450, 450), Image.LANCZOS)
group2_tk = ImageTk.PhotoImage(group2_img)
group2_label = tk.Label(result_frame, image=group2_tk)
group2_label.image = group2_tk
group2_label.grid(row=1, column=1, padx=10)
# Confirmation button
confirm_frame = tk.Frame(preview_window)
confirm_frame.pack(pady=10)
tk.Button(confirm_frame, text="确认并开始批量处理",
command=lambda: [preview_window.destroy(), self.start_processing()]).pack()
def crop_vertebrae_groups(self, image):
"""Crop vertebra groups based on current division lines"""
height, width = image.shape[:2]
# Ensure cut line indices are valid
upper_idx = min(self.upper_cut_line, len(self.divisions) - 1)
lower_idx = min(self.lower_cut_line, len(self.divisions) - 1)
split_idx = min(self.split_line, len(self.divisions) - 1)
# Ensure cut line order is correct
if upper_idx >= lower_idx:
upper_idx, lower_idx = 0, len(self.divisions) - 1
if split_idx <= upper_idx or split_idx >= lower_idx:
split_idx = (upper_idx + lower_idx) // 2
# Get cut line positions
sorted_divisions = sorted(self.divisions)
y_upper = int(height * sorted_divisions[upper_idx])
y_lower = int(height * sorted_divisions[lower_idx])
y_split = int(height * sorted_divisions[split_idx])
# Crop T12-L2 group (upper group) - only keep between upper cut and split lines
top_group = image[y_upper:y_split, :]
# Crop L3-L5 group (lower group) - only keep between split and lower cut lines
bottom_group = image[y_split:y_lower, :]
# Create 512x512 output images, scaled to fill
def create_output_image(region):
if region.size == 0:
return np.zeros((512, 512, 3), dtype=np.uint8)
# Calculate scaling factor - scale to at least 512x512
h, w = region.shape[:2]
# Determine scaling factor to make at least one dimension 512
scale = max(512/h, 512/w)
# Resize
new_h, new_w = int(h * scale), int(w * scale)
resized = cv2.resize(region, (new_w, new_h))
# Create 512x512 canvas
canvas = np.zeros((512, 512, 3), dtype=np.uint8)
# Calculate placement position (centered)
y_offset = (512 - new_h) // 2
x_offset = (512 - new_w) // 2
# Ensure within bounds
y_end = min(512, y_offset + new_h)
x_end = min(512, x_offset + new_w)
# Place resized image on canvas
canvas[y_offset:y_end, x_offset:x_end] = resized[:y_end-y_offset, :x_end-x_offset]
return canvas
group1 = create_output_image(top_group)
group2 = create_output_image(bottom_group)
return group1, group2
def start_processing(self):
"""Start batch processing of images"""
input_dir = self.input_path.get()
output_dir = self.output_path.get()
if not input_dir or not output_dir:
messagebox.showerror("错误", "请先选择输入和输出文件夹")
return
# Create output directory
os.makedirs(output_dir, exist_ok=True)
# Get image files
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif')
image_files = [f for f in os.listdir(input_dir)
if f.lower().endswith(image_extensions)]
if not image_files:
messagebox.showerror("错误", "输入文件夹中没有找到图像文件")
return
# Process images in background thread
self.processing_thread = threading.Thread(
target=self.process_images,
args=(input_dir, output_dir, image_files)
)
self.processing_thread.daemon = True
self.processing_thread.start()
def process_images(self, input_dir, output_dir, image_files):
"""Batch process images"""
# Set up progress bar
self.progress['maximum'] = len(image_files)
self.progress['value'] = 0
# Process each image
success_count = 0
for i, image_file in enumerate(image_files):
self.status_var.set(f"处理中: {image_file}")
self.progress_label.config(text=f"处理中: {image_file} ({i+1}/{len(image_files)})")
# Update GUI
self.root.update_idletasks()
image_path = os.path.join(input_dir, image_file)
try:
image = cv2.imread(image_path)
if image is not None:
# Resize to 512x512 if not already
if image.shape[0] != 512 or image.shape[1] != 512:
image = cv2.resize(image, (512, 512))
# Crop vertebra groups
group1, group2 = self.crop_vertebrae_groups(image)
# Save results
filename = Path(image_file).stem
cv2.imwrite(os.path.join(output_dir, f"{filename}_T12-L2.jpg"), group1)
cv2.imwrite(os.path.join(output_dir, f"{filename}_L3-L5.jpg"), group2)
success_count += 1
else:
logging.warning(f"无法读取图像: {image_file}")
except Exception as e:
logging.error(f"处理图像时出错 {image_file}: {e}")
self.progress['value'] = i + 1
# Finish processing
self.status_var.set(f"处理完成: 成功处理 {success_count}/{len(image_files)} 张图像")
self.progress_label.config(text="处理完成")
messagebox.showinfo("完成", f"处理完成: 成功处理 {success_count}/{len(image_files)} 张图像")
def run(self):
"""Run the application"""
self.root.mainloop()
# Run the application
if __name__ == "__main__":
app = VertebraeCropper()
app.run()