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
class VertebraeCropper:
def __init__(self):
self.root = tk.Tk()
self.root.title("腰椎MRI图像裁剪工具 - 专业版")
self.root.geometry("1200x800")
# 分割线位置(初始化为等分,共7根线将图像分成8份)
self.divisions = [i/8 for i in range(1, 8)]
# 预设管理
self.presets = {}
self.current_preset = "default"
self.load_presets()
# 分割线设置
self.upper_cut_line = 2 # 默认使用第3条线作为上切割线(索引2)
self.lower_cut_line = 5 # 默认使用第6条线作为下切割线(索引5)
self.split_line = 3 # 默认使用第4条线作为分组线(索引3)
# 创建界面元素
self.create_widgets()
def create_widgets(self):
# 主框架
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 标题
title_label = tk.Label(main_frame, text="腰椎MRI图像批量裁剪工具 - 专业版",
font=("Arial", 16, "bold"))
title_label.pack(pady=10)
# 说明文本
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 = tk.Frame(main_frame)
control_frame.pack(pady=10, fill=tk.X)
# 预设管理框架
preset_frame = tk.Frame(control_frame)
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)
# 输入文件夹选择
input_frame = tk.Frame(control_frame)
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_frame = tk.Frame(control_frame)
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)
# 按钮框架
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_frame = tk.Frame(main_frame)
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)
# 状态栏
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)
# 当前图像和分割线信息
self.current_image = None
self.current_image_path = None
def load_presets(self):
"""加载预设"""
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)]})
# 加载切割线设置
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:
self.presets = {"default": [i/8 for i in range(1, 8)]}
def save_presets(self):
"""保存预设"""
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)
def load_selected_preset(self, event=None):
"""加载选中的预设"""
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):
"""保存当前预设"""
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):
"""创建新预设"""
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):
"""删除当前预设"""
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):
folder = filedialog.askdirectory(title="选择包含MRI图像的文件夹")
if folder:
self.input_path.set(folder)
def select_output_folder(self):
folder = filedialog.askdirectory(title="选择保存结果的文件夹")
if folder:
self.output_path.set(folder)
def detect_vertebrae_centers(self, image):
"""检测椎体中心位置"""
# 转换为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 应用高斯模糊减少噪声
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 使用自适应阈值处理
thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2)
# 形态学操作去除小噪声
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 筛选出可能是椎体的轮廓
vertebra_centers = []
for contour in contours:
area = cv2.contourArea(contour)
if area < 500 or area > 10000: # 过滤太大或太小的区域
continue
# 计算轮廓的边界矩形
x, y, w, h = cv2.boundingRect(contour)
aspect_ratio = w / h
# 根据纵横比筛选
if 0.5 < aspect_ratio < 2.0:
# 计算轮廓的中心
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))
# 按y坐标排序(从上到下)
vertebra_centers.sort(key=lambda center: center[1])
return vertebra_centers
def preview_and_adjust(self):
"""预览图像并允许调整分割线"""
input_dir = self.input_path.get()
if not input_dir:
messagebox.showerror("错误", "请先选择输入文件夹")
return
# 获取第一个图像文件
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff')
image_files = [f for f in os.listdir(input_dir)
if f.lower().endswith(image_extensions)]
if not image_files:
messagebox.showerror("错误", "输入文件夹中没有找到图像文件")
return
# 加载第一张图像
self.current_image_path = os.path.join(input_dir, image_files[0])
self.current_image = cv2.imread(self.current_image_path)
if self.current_image is None:
messagebox.showerror("错误", "无法读取图像文件")
return
# 调整大小为512x512(如果还不是)
if self.current_image.shape[0] != 512 or self.current_image.shape[1] != 512:
self.current_image = cv2.resize(self.current_image, (512, 512))
# 创建交互式分割调整界面
self.create_adjustment_ui()
def create_adjustment_ui(self):
"""创建分割调整界面"""
# 创建新窗口
self.adjust_window = tk.Toplevel(self.root)
self.adjust_window.title("调整分割线")
self.adjust_window.geometry("1200x800")
# 创建Matplotlib图形
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)
# 添加控制按钮的框架
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_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)
# 创建子图
ax = fig.add_subplot(111)
ax.imshow(cv2.cvtColor(self.current_image, cv2.COLOR_BGR2RGB))
ax.set_title('调整分割线位置 (拖拽红线调整,点击按钮添加/删除分割线)')
# 绘制初始分割线
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)
# 添加分割线标签
ax.text(10, y_pos + 5, f"线 {i+1}", color=color,
bbox=dict(facecolor='white', alpha=0.7))
# 绘制椎体分组标签
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))
# 设置交互功能
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)
fig.tight_layout()
self.canvas.draw()
def on_pick(self, event):
"""当用户选择一条分割线时"""
if event.artist in self.division_lines:
self.current_dragging_line = event.artist
def on_motion(self, event):
"""当用户拖动分割线时"""
if self.current_dragging_line is not None and event.ydata is not None:
# 限制分割线在图像范围内
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):
"""当用户释放鼠标时"""
self.current_dragging_line = None
def add_division(self):
"""添加分割线"""
# 在中间位置添加新分割线
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)
# 添加标签
ax.text(10, new_y + 5, f"线 {len(self.division_lines)}", color='red',
bbox=dict(facecolor='white', alpha=0.7))
# 更新分割线选择框的范围
self.update_spinbox_ranges()
self.canvas.draw()
def remove_division(self):
"""删除最后一条分割线"""
if len(self.division_lines) > 1: # 至少保留一条分割线
line = self.division_lines.pop()
line.remove()
# 更新分割线选择框的范围
self.update_spinbox_ranges()
self.canvas.draw()
else:
messagebox.showwarning("警告", "至少需要保留一条分割线")
def update_spinbox_ranges(self):
"""更新分割线选择框的范围"""
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):
"""保存当前分割线设置"""
# 更新切割线设置
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()
# 保存到文件
self.save_presets()
messagebox.showinfo("成功", "分割线设置已保存")
def confirm_divisions(self):
"""确认当前分割线位置(应用)"""
# 更新分割线位置(归一化)
height = self.current_image.shape[0]
self.divisions = [line.get_ydata()[0] / height for line in self.division_lines]
# 更新切割线设置
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()
# 排序分割线
self.divisions.sort()
# 关闭调整窗口
self.adjust_window.destroy()
# 显示预览结果
self.show_preview_result()
def reset_divisions(self):
"""重置分割线到初始位置"""
height = self.current_image.shape[0]
# 重置为等分
self.divisions = [i/8 for i in range(1, 8)]
# 清除所有分割线
for line in self.division_lines:
line.remove()
self.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)
# 添加分割线标签
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):
"""自动检测椎体并设置分割线"""
vertebra_centers = self.detect_vertebrae_centers(self.current_image)
if len(vertebra_centers) >= 6:
# 获取椎体的y坐标
y_positions = [center[1] for center in vertebra_centers[:6]]
# 计算分割线位置(在椎体之间)
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])
# 添加额外的分割线以保持总数
while len(new_divisions) < 7: # 确保有7条分割线
new_divisions.append(new_divisions[-1] + 0.05)
# 更新分割线
height = self.current_image.shape[0]
# 清除所有分割线
for line in self.division_lines:
line.remove()
self.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)
# 添加分割线标签
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):
"""显示预览结果"""
# 创建新窗口显示裁剪结果
preview_window = tk.Toplevel(self.root)
preview_window.title("预览裁剪结果")
preview_window.geometry("1200x600")
# 裁剪图像
group1, group2 = self.crop_vertebrae_groups(self.current_image)
# 显示结果
result_frame = tk.Frame(preview_window)
result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 显示T12-L2组
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)
# 显示L3-L5组
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)
# 确认按钮
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):
"""根据当前分割线裁剪椎体组"""
height, width = image.shape[:2]
# 确保切割线索引有效
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)
# 确保切割线顺序正确
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
# 获取切割线位置
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])
# 裁剪T12-L2组(上组)- 只保留上切割线和分组线之间的部分
top_group = image[y_upper:y_split, :]
# 裁剪L3-L5组(下组)- 只保留分组线和下切割线之间的部分
bottom_group = image[y_split:y_lower, :]
# 创建512x512的输出图像,放大并铺满
def create_output_image(region):
if region.size == 0:
return np.zeros((512, 512, 3), dtype=np.uint8)
# 计算缩放比例 - 放大到至少512x512
h, w = region.shape[:2]
# 确定缩放比例,使图像至少有一个维度达到512
scale = max(512/h, 512/w)
# 调整大小
new_h, new_w = int(h * scale), int(w * scale)
resized = cv2.resize(region, (new_w, new_h))
# 创建512x512的画布
canvas = np.zeros((512, 512, 3), dtype=np.uint8)
# 计算放置位置(居中)
y_offset = (512 - new_h) // 2
x_offset = (512 - new_w) // 2
# 确保不超出边界
y_end = min(512, y_offset + new_h)
x_end = min(512, x_offset + new_w)
# 将调整大小后的图像放入画布中
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):
"""开始批量处理图像"""
input_dir = self.input_path.get()
output_dir = self.output_path.get()
if not input_dir or not output_dir:
messagebox.showerror("错误", "请先选择输入和输出文件夹")
return
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 获取图像文件
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff')
image_files = [f for f in os.listdir(input_dir)
if f.lower().endswith(image_extensions)]
if not image_files:
messagebox.showerror("错误", "输入文件夹中没有找到图像文件")
return
# 在后台线程中处理图像
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):
"""批量处理图像"""
# 设置进度条
self.progress['maximum'] = len(image_files)
self.progress['value'] = 0
# 处理每个图像
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)})")
# 更新GUI
self.root.update_idletasks()
image_path = os.path.join(input_dir, image_file)
image = cv2.imread(image_path)
if image is not None:
# 调整大小为512x512(如果还不是)
if image.shape[0] != 512 or image.shape[1] != 512:
image = cv2.resize(image, (512, 512))
# 裁剪椎体组
group1, group2 = self.crop_vertebrae_groups(image)
# 保存结果
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
self.progress['value'] = i + 1
# 完成处理
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):
self.root.mainloop()
# 运行程序
if __name__ == "__main__":
app = VertebraeCropper()
app.run()