/
rodion_1521
/
EntropyMapSegmentation
Обзор
Документация
Войти
/
rodion_1521
/
EntropyMapSegmentation
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
EntropySegmentationApp.py
769 строк
34 KB
rodion_1521
refactoring
06 июн 2025, 07:37
06 июн 2025, 07:37
75181a2
Код
Авторство
О чём код?
import os import time import cv2 import numpy as np import pandas as pd from scipy.ndimage import generic_filter from sklearn.cluster import MiniBatchKMeans from PIL import Image, ImageTk import tkinter as tk from tkinter import filedialog, messagebox, ttk from numba import jit import threading from datetime import datetime from openpyxl import Workbook from openpyxl.styles import PatternFill from openpyxl.utils.dataframe import dataframe_to_rows from Utils.Timer import Timer class EntropyCalculator: @staticmethod @jit(nopython=True) def entropy_filter_numba(window): bins = 64 hist = np.zeros(bins) min_val, max_val = 0, 256 bin_size = (max_val - min_val) / bins for pixel in window: bin_idx = int((pixel - min_val) / bin_size) if bin_idx >= bins: bin_idx = bins - 1 hist[bin_idx] += 1 hist = hist / (window.size + 1e-10) hist = hist + 1e-10 return -np.sum(hist * np.log2(hist)) def calculate_entropy_map(self, image, window_size=5): try: small_img = cv2.resize(image, (256, 256), interpolation=cv2.INTER_AREA) entropy_map = generic_filter(small_img, self.entropy_filter_numba, size=window_size, mode='reflect') entropy_map = cv2.resize(entropy_map, (512, 512), interpolation=cv2.INTER_CUBIC) entropy_map = (entropy_map - entropy_map.min()) / (entropy_map.max() - entropy_map.min() + 1e-10) * 255 return entropy_map.astype(np.uint8) except Exception as e: messagebox.showerror("Ошибка", f"Ошибка вычисления энтропии: {str(e)}") return None class ImageProcessor: def __init__(self): self.target_size = (512, 512) self.display_size = (350, 350) def resize_image(self, image, target_size): try: h, w = image.shape[:2] target_h, target_w = target_size scale = min(target_w / w, target_h / h) new_w, new_h = int(w * scale), int(h * scale) resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA) if len(image.shape) == 2: padded = np.zeros((target_h, target_w), dtype=np.uint8) else: padded = np.zeros((target_h, target_w, 3), dtype=np.uint8) x_offset = (target_w - new_w) // 2 y_offset = (target_h - new_h) // 2 padded[y_offset:y_offset + new_h, x_offset:x_offset + new_w] = resized return padded except Exception as e: messagebox.showerror("Ошибка", f"Ошибка изменения размера изображения: {str(e)}") return None def segment_image(self, entropy_map, closing_kernel_size=7, threshold_adjust=127): try: entropy_map = cv2.GaussianBlur(entropy_map, (5, 5), 0) small_entropy = cv2.resize(entropy_map, (128, 128), interpolation=cv2.INTER_AREA) kmeans = MiniBatchKMeans(n_clusters=2, random_state=0) labels = kmeans.fit_predict(small_entropy.reshape(-1, 1)) labels = labels.reshape(small_entropy.shape) object_label = np.argmax([np.mean(small_entropy[labels == i]) for i in range(2)]) mask = (labels == object_label).astype(np.uint8) * 255 mask = cv2.resize(mask, self.target_size, interpolation=cv2.INTER_NEAREST) _, mask = cv2.threshold(mask, threshold_adjust, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: largest_contour = max(contours, key=cv2.contourArea) filled_mask = np.zeros_like(mask) cv2.fillPoly(filled_mask, [largest_contour], 255) else: filled_mask = mask print("Предупреждение: Контуры в маске не найдены.") kernel = np.ones((closing_kernel_size, closing_kernel_size), np.uint8) filled_mask = cv2.morphologyEx(filled_mask, cv2.MORPH_OPEN, kernel, iterations=1) if np.sum(filled_mask) == 0: print("Предупреждение: Сегментационная маска пуста. Настройте window_size или threshold_adjust.") return filled_mask except Exception as e: messagebox.showerror("Ошибка", f"Ошибка сегментации: {str(e)}") return None class Benchmark: def __init__(self, target_size=(512, 512)): self.target_size = target_size self.mask_folder = None self.results = [] def compare_single_image(self, image_name, pred_mask): gt_mask = self.load_ground_truth(image_name) if gt_mask is None: return {"IoU": 0.0, "Dice": 0.0, "Accuracy": 0.0}, None if gt_mask.shape != pred_mask.shape: gt_mask = cv2.resize(gt_mask, (pred_mask.shape[1], pred_mask.shape[0]), interpolation=cv2.INTER_NEAREST) metrics = self.compute_metrics(pred_mask, gt_mask) return metrics, gt_mask def set_mask_folder(self, folder_path): if folder_path and os.path.exists(folder_path): self.mask_folder = folder_path return True return False def load_ground_truth(self, image_name): if not self.mask_folder: return None try: # Ищем маску с заменой 'image' на 'mask' в названии файла base_name = os.path.splitext(image_name)[0] if 'image' in base_name.lower(): mask_name = base_name.lower().replace('image', 'mask') + '.png' else: mask_name = 'mask_' + base_name + '.png' mask_path = os.path.join(self.mask_folder, mask_name) if not os.path.exists(mask_path): # Если не нашли по шаблону, пробуем найти любой файл с тем же именем for f in os.listdir(self.mask_folder): if os.path.splitext(f)[0] == os.path.splitext(image_name)[0]: mask_path = os.path.join(self.mask_folder, f) break else: print(f"Эталонная маска не найдена для: {image_name}") return None mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) if mask is None: print(f"Не удалось загрузить эталонную маску: {mask_path}") return None # Возвращаем маску без изменения размера _, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY) return mask except Exception as e: print(f"Ошибка загрузки эталонной маски: {str(e)}") return None def compute_metrics(self, pred_mask, gt_mask): if pred_mask is None or gt_mask is None: return {"IoU": 0.0, "Dice": 0.0, "Accuracy": 0.0} pred_mask = (pred_mask > 127).astype(np.uint8) gt_mask = (gt_mask > 127).astype(np.uint8) intersection = np.logical_and(pred_mask, gt_mask).sum() union = np.logical_or(pred_mask, gt_mask).sum() total_pixels = pred_mask.size iou = intersection / (union + 1e-10) dice = (2 * intersection) / (pred_mask.sum() + gt_mask.sum() + 1e-10) accuracy = (pred_mask == gt_mask).sum() / total_pixels return {"IoU": iou, "Dice": dice, "Accuracy": accuracy} def add_result(self, image_name, algorithm, params, metrics, elapsed_time): self.results.append({ "Image": image_name, "Algorithm": algorithm, "Parameters": str(params), **metrics, "Time (s)": round(elapsed_time, 2) }) def save_to_excel(self, filename="benchmark_results.xlsx"): if not self.results: messagebox.showwarning("Предупреждение", "Нет данных для сохранения") return df = pd.DataFrame(self.results) # Группируем по изображению и ищем наилучший IoU best_iou_per_image = df.groupby("Image")["IoU"].transform("max") # Создаём Excel-файл вручную через openpyxl wb = Workbook() ws = wb.active ws.title = "Benchmark Results" green_fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid") # светло-зелёный red_fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid") # светло-красный # Добавляем заголовки for r in dataframe_to_rows(df, index=False, header=True): ws.append(r) # Применяем стили по метрике IoU for row_idx, (row_data, best_iou) in enumerate(zip(df.itertuples(index=False), best_iou_per_image), start=2): iou = getattr(row_data, "IoU", 0) iou_cell = ws.cell(row=row_idx, column=df.columns.get_loc("IoU") + 1) if abs(iou - best_iou) < 1e-5: iou_cell.fill = green_fill else: iou_cell.fill = red_fill # Сохраняем файл с временной меткой timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") final_filename = f"benchmark_results_{timestamp}.xlsx" wb.save(final_filename) messagebox.showinfo("Успех", f"Результаты сохранены в {final_filename}") class AppState: MAIN_MENU = 0 SINGLE_IMAGE = 1 BENCHMARK = 2 class ImageSegmentationApp: def __init__(self, root): self.root = root self.root.title("Инструмент сегментации изображений") self.root.geometry("1600x800") self.root.configure(bg="#f0f0f0") self.state = AppState.MAIN_MENU self.image_files = [] self.current_index = 0 self.window_size = 5 self.closing_kernel_size = 7 self.threshold_adjust = 50 self.target_size = (512, 512) self.display_size = (350, 350) self.image_processor = ImageProcessor() self.entropy_calculator = EntropyCalculator() self.benchmark = Benchmark(self.target_size) self.setup_styles() self.create_main_menu() def setup_styles(self): self.style = ttk.Style() self.style.configure("TFrame", background="#f0f0f0") self.style.configure("TButton", padding=10, font=('Helvetica', 12)) self.style.configure("Header.TLabel", background="#f0f0f0", font=('Helvetica', 14, 'bold')) self.style.configure("Title.TLabel", background="#f0f0f0", font=('Helvetica', 18, 'bold')) def clear_frame(self): for widget in self.root.winfo_children(): widget.destroy() def create_main_menu(self): self.clear_frame() self.state = AppState.MAIN_MENU main_frame = ttk.Frame(self.root) main_frame.pack(expand=True, fill=tk.BOTH, padx=50, pady=50) title_label = ttk.Label(main_frame, text="Главное меню", style="Title.TLabel") title_label.pack(pady=(0, 50)) btn_frame = ttk.Frame(main_frame) btn_frame.pack() single_btn = ttk.Button(btn_frame, text="Одиночная обработка", command=self.create_single_image_ui, width=20) single_btn.pack(pady=10) benchmark_btn = ttk.Button(btn_frame, text="Бенчмарк", command=self.create_benchmark_ui, width=20) benchmark_btn.pack(pady=10) exit_btn = ttk.Button(btn_frame, text="Выход", command=self.root.quit, width=20) exit_btn.pack(pady=10) def create_single_image_ui(self): self.clear_frame() self.state = AppState.SINGLE_IMAGE main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Control panel control_frame = ttk.Frame(main_frame) control_frame.pack(fill=tk.X, pady=(0, 10)) back_btn = ttk.Button(control_frame, text="← Назад", command=self.create_main_menu) back_btn.pack(side=tk.LEFT, padx=5) self.img_folder_btn = ttk.Button(control_frame, text="Выбрать папку с изображениями", command=self.load_image_folder) self.img_folder_btn.pack(side=tk.LEFT, padx=5) self.mask_folder_btn = ttk.Button(control_frame, text="Выбрать папку с масками", command=self.load_mask_folder) self.mask_folder_btn.pack(side=tk.LEFT, padx=5) self.prev_btn = ttk.Button(control_frame, text="◄ Назад", command=self.show_prev_image, state=tk.DISABLED) self.prev_btn.pack(side=tk.LEFT, padx=5) self.next_btn = ttk.Button(control_frame, text="Вперёд ►", command=self.show_next_image, state=tk.DISABLED) self.next_btn.pack(side=tk.LEFT, padx=5) # Sliders slider_frame = ttk.Frame(control_frame) slider_frame.pack(side=tk.LEFT, expand=True, fill=tk.X) ttk.Label(slider_frame, text="Размер окна:").pack(anchor=tk.W) self.window_slider = ttk.Scale(slider_frame, from_=3, to=15, value=5, command=lambda v: self.update_param("window", int(float(v)))) self.window_slider.pack(fill=tk.X) self.window_value = ttk.Label(slider_frame, text="5") self.window_value.pack(anchor=tk.W) ttk.Label(slider_frame, text="Размер ядра:").pack(anchor=tk.W, pady=(10, 0)) self.closing_kernel_slider = ttk.Scale(slider_frame, from_=3, to=15, value=7, command=lambda v: self.update_param("closing_kernel", int(float(v)))) self.closing_kernel_slider.pack(fill=tk.X) self.closing_kernel_value = ttk.Label(slider_frame, text="7") self.closing_kernel_value.pack(anchor=tk.W) ttk.Label(slider_frame, text="Порог:").pack(anchor=tk.W, pady=(10, 0)) self.threshold_slider = ttk.Scale(slider_frame, from_=0, to=100, value=50, command=lambda v: self.update_param("threshold", int(float(v)))) self.threshold_slider.pack(fill=tk.X) self.threshold_value = ttk.Label(slider_frame, text="50") self.threshold_value.pack(anchor=tk.W) self.file_label = ttk.Label(control_frame, text="Изображение не загружено", style="Header.TLabel") self.file_label.pack(side=tk.RIGHT, padx=10) # Image display img_frame = ttk.Frame(main_frame) img_frame.pack(fill=tk.BOTH, expand=True) orig_frame = ttk.Frame(img_frame) orig_frame.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=5) ttk.Label(orig_frame, text="Исходное изображение", style="Header.TLabel").pack() self.orig_canvas = tk.Canvas(orig_frame, width=self.display_size[0], height=self.display_size[1], bg="white") self.orig_canvas.pack() entropy_frame = ttk.Frame(img_frame) entropy_frame.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=5) ttk.Label(entropy_frame, text="Карта энтропии", style="Header.TLabel").pack() self.entropy_canvas = tk.Canvas(entropy_frame, width=self.display_size[0], height=self.display_size[1], bg="white") self.entropy_canvas.pack() mask_frame = ttk.Frame(img_frame) mask_frame.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=5) ttk.Label(mask_frame, text="Сегментация", style="Header.TLabel").pack() self.mask_canvas = tk.Canvas(mask_frame, width=self.display_size[0], height=self.display_size[1], bg="white") self.mask_canvas.pack() gt_frame = ttk.Frame(img_frame) gt_frame.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=5) ttk.Label(gt_frame, text="Эталонная маска", style="Header.TLabel").pack() self.gt_canvas = tk.Canvas(gt_frame, width=self.display_size[0], height=self.display_size[1], bg="white") self.gt_canvas.pack() # Metrics metrics_frame = ttk.Frame(main_frame) metrics_frame.pack(fill=tk.X, pady=10) self.metrics_label = ttk.Label(metrics_frame, text="Метрики: Н/Д", font=('Helvetica', 12)) self.metrics_label.pack() def create_benchmark_ui(self): self.clear_frame() self.state = AppState.BENCHMARK main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Title and back button header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 20)) back_btn = ttk.Button(header_frame, text="← Назад", command=self.create_main_menu) back_btn.pack(side=tk.LEFT) title_label = ttk.Label(header_frame, text="Режим бенчмарка", style="Title.TLabel") title_label.pack(side=tk.LEFT, expand=True) # Control panel control_frame = ttk.Frame(main_frame) control_frame.pack(fill=tk.X, pady=(0, 10)) self.img_folder_btn = ttk.Button(control_frame, text="Выбрать папку с изображениями", command=self.select_benchmark_images) self.img_folder_btn.pack(side=tk.LEFT, padx=5) self.mask_folder_btn = ttk.Button(control_frame, text="Выбрать папку с масками", command=self.select_benchmark_masks) self.mask_folder_btn.pack(side=tk.LEFT, padx=5) self.run_btn = ttk.Button(control_frame, text="Запуск бенчмарка", command=self.run_benchmark, state=tk.DISABLED) self.run_btn.pack(side=tk.LEFT, padx=5) # Status self.status_label = ttk.Label(control_frame, text="Выберите папки с изображениями и масками") self.status_label.pack(side=tk.RIGHT, padx=10) # Progress self.progress_frame = ttk.Frame(main_frame) self.progress_frame.pack(fill=tk.X, pady=10) self.progress_label = ttk.Label(self.progress_frame, text="Готов к работе") self.progress_label.pack() self.progress_bar = ttk.Progressbar(self.progress_frame, orient="horizontal", length=400, mode="determinate") self.progress_bar.pack() # Results table results_frame = ttk.Frame(main_frame) results_frame.pack(fill=tk.BOTH, expand=True) self.results_tree = ttk.Treeview( results_frame, columns=("Image", "Algorithm", "Params", "Time", "IoU", "Dice", "Accuracy"), show="headings" ) self.results_tree.heading("Image", text="Изображение") self.results_tree.heading("Algorithm", text="Алгоритм") self.results_tree.heading("Params", text="Параметры") self.results_tree.heading("Time", text="Время") self.results_tree.heading("IoU", text="IoU") self.results_tree.heading("Dice", text="Dice") self.results_tree.heading("Accuracy", text="Accuracy") self.results_tree.column("Image", width=200) self.results_tree.column("Algorithm", width=150) self.results_tree.column("Params", width=200) self.results_tree.column("Time",width=200) self.results_tree.column("IoU", width=100) self.results_tree.column("Dice", width=100) self.results_tree.column("Accuracy", width=100) self.results_tree.pack(fill=tk.BOTH, expand=True) save_btn = ttk.Button(main_frame, text="Сохранить результаты", command=self.save_benchmark_results) save_btn.pack(pady=10) def load_image_folder(self): folder_path = filedialog.askdirectory(title="Выберите папку с изображениями") if folder_path: self.image_files = [f for f in os.listdir(folder_path) if f.lower().endswith(('.png', '.jpg', '.jpeg'))] if not self.image_files: messagebox.showwarning("Предупреждение", "В папке нет изображений!") return self.folder_path = folder_path self.current_index = 0 self.prev_btn.config(state=tk.DISABLED) self.next_btn.config(state=tk.NORMAL if len(self.image_files) > 1 else tk.DISABLED) self.show_image() def load_mask_folder(self): folder_path = filedialog.askdirectory(title="Выберите папку с масками") if folder_path: self.benchmark.set_mask_folder(folder_path) if self.image_files: self.show_image() def select_benchmark_images(self): folder_path = filedialog.askdirectory(title="Выберите папку с изображениями") if folder_path: self.benchmark_images_folder = folder_path self.image_files = [f for f in os.listdir(folder_path) if f.lower().endswith(('.png', '.jpg', '.jpeg'))] self.status_label.config(text=f"Изображений: {len(self.image_files)}") self.check_benchmark_ready() def select_benchmark_masks(self): folder_path = filedialog.askdirectory(title="Выберите папку с масками") if folder_path: self.benchmark.set_mask_folder(folder_path) self.status_label.config(text=f"Маски: {folder_path}") self.check_benchmark_ready() def check_benchmark_ready(self): if hasattr(self, 'benchmark_images_folder') and self.benchmark.mask_folder: self.run_btn.config(state=tk.NORMAL) self.status_label.config(text="Готов к запуску") def run_benchmark(self): if not hasattr(self, 'benchmark_images_folder') or not self.benchmark.mask_folder: messagebox.showerror("Ошибка", "Сначала выберите папки с изображениями и масками") return self.progress_bar["value"] = 0 self.progress_bar["maximum"] = len(self.image_files) self.benchmark.results = [] self.results_tree.delete(*self.results_tree.get_children()) def benchmark_thread(): try: for i, image_name in enumerate(self.image_files): self.progress_label.config(text=f"Обработка {image_name} ({i + 1}/{len(self.image_files)})") img_path = os.path.join(self.benchmark_images_folder, image_name) img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: continue processed_img = self.image_processor.resize_image(img, self.target_size) if processed_img is None: continue results_per_image = [] # === Алгоритм "Энтропия" === for window_size in [5, 7, 9]: entropy_map_time_start = time.time() entropy_map = self.entropy_calculator.calculate_entropy_map(processed_img, window_size) entropy_map_time_elapsed = time.time() - entropy_map_time_start if entropy_map is None: continue for threshold in [50, 100, 150]: entropy_mask_time_start = time.time() mask = self.image_processor.segment_image(entropy_map, 7, threshold) entropy_map_time_elapsed = (time.time() - entropy_mask_time_start) + entropy_map_time_elapsed if mask is None: continue metrics, _ = self.benchmark.compare_single_image(image_name, mask) params = {"window_size": window_size, "threshold": threshold} self.benchmark.add_result(image_name, "Энтропия", params, metrics, entropy_map_time_elapsed) result = { "Image": image_name, "Algorithm": "Энтропия", "Params": f"{params}", **metrics, } results_per_image.append(result) # === Базовый алгоритм: Otsu === otsu_time_start = time.time() _, otsu_mask = cv2.threshold(processed_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) metrics_otsu, _ = self.benchmark.compare_single_image(image_name, otsu_mask) otsu_time_elapsed = time.time() - otsu_time_start self.benchmark.add_result(image_name, "Otsu", {}, metrics_otsu, otsu_time_elapsed) results_per_image.append({ "Image": image_name, "Algorithm": "Otsu", "Params": "auto", **metrics_otsu, }) # === Базовый алгоритм: Adaptive Threshold === adaptive_time_start = time.time() adaptive_mask = cv2.adaptiveThreshold(processed_img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2) metrics_adapt, _ = self.benchmark.compare_single_image(image_name, adaptive_mask) adaptive_time_elapsed = time.time() - adaptive_time_start self.benchmark.add_result(image_name, "Adaptive", {}, metrics_adapt, adaptive_time_elapsed) results_per_image.append({ "Image": image_name, "Algorithm": "Adaptive", "Params": "blockSize=11, C=2", **metrics_adapt }) # === Выделение лучшего === best_result = max(results_per_image, key=lambda x: x["IoU"]) for result in results_per_image: color_tag = "" if result["IoU"] == best_result["IoU"]: color_tag = "green" if result["Algorithm"] == "Энтропия" else "red" self.root.after(0, self.add_benchmark_result, { "Image": result["Image"], "Algorithm": result["Algorithm"], "Params": result["Params"], "IoU": f"{result['IoU']:.3f}", "Dice": f"{result['Dice']:.3f}", "Accuracy": f"{result['Accuracy']:.3f}", "color_tag": color_tag }) self.progress_bar["value"] = i + 1 self.root.update() self.progress_label.config(text="Бенчмарк завершен") messagebox.showinfo("Успех", "Бенчмарк успешно завершен") except Exception as e: messagebox.showerror("Ошибка", f"Ошибка при выполнении бенчмарка: {str(e)}") threading.Thread(target=benchmark_thread, daemon=True).start() def add_benchmark_result(self, result): values = ( result["Image"], result["Algorithm"], result["Params"], result["IoU"], result["Dice"], result["Accuracy"] ) tag = "" if "color_tag" in result: tag = result["color_tag"] self.results_tree.insert("", "end", values=values, tags=(tag,)) def save_benchmark_results(self): self.benchmark.save_to_excel() def process_image(self): if not self.image_files: return try: image_name = self.image_files[self.current_index] img_path = os.path.join(self.folder_path, image_name) img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: raise ValueError(f"Не удалось загрузить изображение: {img_path}") # Обрабатываем основное изображение processed_img = self.image_processor.resize_image(img, self.target_size) if processed_img is None: return entropy_map = self.entropy_calculator.calculate_entropy_map(processed_img, self.window_size) if entropy_map is None: return threshold_value = int(self.threshold_adjust * 255 / 100) mask = self.image_processor.segment_image(entropy_map, self.closing_kernel_size, threshold_value) if mask is None: return # Получаем эталонную маску и метрики metrics, gt_mask = self.benchmark.compare_single_image(image_name, mask) # Подготовка изображений для отображения display_img = self.image_processor.resize_image(processed_img, self.display_size) display_entropy = self.image_processor.resize_image(entropy_map, self.display_size) display_mask = self.image_processor.resize_image(mask, self.display_size) # Подготовка эталонной маски для отображения (с сохранением пропорций) if gt_mask is not None: # Масштабируем маску с сохранением пропорций h, w = gt_mask.shape[:2] scale = min(self.display_size[0] / w, self.display_size[1] / h) new_w, new_h = int(w * scale), int(h * scale) display_gt = cv2.resize(gt_mask, (new_w, new_h), interpolation=cv2.INTER_NEAREST) # Создаем изображение нужного размера с черным фоном padded_gt = np.zeros((self.display_size[1], self.display_size[0]), dtype=np.uint8) x_offset = (self.display_size[0] - new_w) // 2 y_offset = (self.display_size[1] - new_h) // 2 padded_gt[y_offset:y_offset + new_h, x_offset:x_offset + new_w] = display_gt display_gt = padded_gt else: display_gt = None # Конвертация в RGB для отображения display_img = cv2.cvtColor(display_img, cv2.COLOR_GRAY2RGB) display_entropy = cv2.cvtColor(display_entropy, cv2.COLOR_GRAY2RGB) display_mask = cv2.cvtColor(display_mask, cv2.COLOR_GRAY2RGB) if display_gt is not None: display_gt = cv2.cvtColor(display_gt, cv2.COLOR_GRAY2RGB) self.update_display(display_img, display_entropy, display_mask, display_gt, metrics) self.file_label.config(text=image_name) except Exception as e: messagebox.showerror("Ошибка", str(e)) def update_display(self, img, entropy, mask, gt_mask=None, metrics=None): self.orig_canvas.delete("all") self.entropy_canvas.delete("all") self.mask_canvas.delete("all") self.gt_canvas.delete("all") self.img_tk = ImageTk.PhotoImage(Image.fromarray(img)) self.entropy_tk = ImageTk.PhotoImage(Image.fromarray(entropy)) self.mask_tk = ImageTk.PhotoImage(Image.fromarray(mask)) self.orig_canvas.create_image(0, 0, anchor=tk.NW, image=self.img_tk) self.entropy_canvas.create_image(0, 0, anchor=tk.NW, image=self.entropy_tk) self.mask_canvas.create_image(0, 0, anchor=tk.NW, image=self.mask_tk) if gt_mask is not None: self.gt_tk = ImageTk.PhotoImage(Image.fromarray(gt_mask)) self.gt_canvas.create_image(0, 0, anchor=tk.NW, image=self.gt_tk) else: self.gt_canvas.create_text(self.display_size[0] // 2, self.display_size[1] // 2, text="Эталонная маска не найдена", font=('Helvetica', 10)) if metrics: metrics_text = f"IoU: {metrics['IoU']:.3f} | Dice: {metrics['Dice']:.3f} | Accuracy: {metrics['Accuracy']:.3f}" self.metrics_label.config(text=metrics_text) else: self.metrics_label.config(text="Метрики: Н/Д") def update_param(self, param_type, value): if param_type == "window": self.window_size = value self.window_value.config(text=str(value)) elif param_type == "closing_kernel": self.closing_kernel_size = value self.closing_kernel_value.config(text=str(value)) elif param_type == "threshold": self.threshold_adjust = value self.threshold_value.config(text=str(value)) if self.image_files and self.state == AppState.SINGLE_IMAGE: self.show_image() def show_prev_image(self): if self.current_index > 0: self.current_index -= 1 self.show_image() self.next_btn.config(state=tk.NORMAL) if self.current_index == 0: self.prev_btn.config(state=tk.DISABLED) def show_next_image(self): if self.current_index < len(self.image_files) - 1: self.current_index += 1 self.show_image() self.prev_btn.config(state=tk.NORMAL) if self.current_index == len(self.image_files) - 1: self.next_btn.config(state=tk.DISABLED) def show_image(self): if self.state == AppState.SINGLE_IMAGE: threading.Thread(target=self.process_image, daemon=True).start() if __name__ == "__main__": root = tk.Tk() app = ImageSegmentationApp(root) root.mainloop()