#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
图像AOI编辑器
支持打开图片、缩放、创建AOI区域、编辑AOI属性等功能
"""

import sys
import os
import json
from typing import List, Dict, Optional, Tuple
import cv2
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QFileDialog,
                             QMessageBox, QColorDialog, QTreeWidgetItem,
                             QGraphicsView, QGraphicsScene, QGraphicsPixmapItem,
                             QGraphicsRectItem, QWidget, QGraphicsItem, QDialog,
                             QVBoxLayout, QHBoxLayout, QPushButton, QComboBox,
                             QSlider, QSpinBox)
from PyQt5.QtCore import Qt, pyqtSignal, QRectF, QPointF, QObject
from PyQt5.QtGui import (QPixmap, QImage, QPen, QBrush, QColor, QPainter,
                         QWheelEvent, QMouseEvent, QCursor)
from PyQt5 import uic

try:
    import vtk
    from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
    VTK_AVAILABLE = True
except ImportError:
    VTK_AVAILABLE = False
    print("VTK not available. Please install vtk: pip install vtk")
def save_image_chinese_path(image, file_path):
    """
    保存图像到中文路径
    
    Args:
        image: OpenCV图像数组
        file_path: 包含中文的文件路径
    
    Returns:
        bool: 是否保存成功
    """
    try:
        # 获取文件扩展名
        ext = file_path.lower().split('.')[-1]
        
        # 根据扩展名设置编码格式
        if ext in ['jpg', 'jpeg']:
            encode_param = [cv2.IMWRITE_JPEG_QUALITY, 95]
            success, encoded_img = cv2.imencode('.jpg', image, encode_param)
        elif ext == 'png':
            encode_param = [cv2.IMWRITE_PNG_COMPRESSION, 3]
            success, encoded_img = cv2.imencode('.png', image, encode_param)
        elif ext in ['bmp']:
            success, encoded_img = cv2.imencode('.bmp', image)
        elif ext in ['tif', 'tiff']:
            success, encoded_img = cv2.imencode('.tiff', image)
        else:
            # 默认使用jpg格式
            encode_param = [cv2.IMWRITE_JPEG_QUALITY, 95]
            success, encoded_img = cv2.imencode('.jpg', image, encode_param)
        
        if success:
            # 使用numpy的tofile方法支持中文路径
            encoded_img.tofile(file_path)
            return True
        else:
            return False
            
    except Exception as e:
        print(f"保存图像失败: {e}")
        return False


class ChannelSelector(QDialog):
    """通道选择对话框"""
    
    def __init__(self, image_shape, parent=None):
        super().__init__(parent)
        self.image_shape = image_shape
        self.selected_channel = 0
        self.setup_ui()
    
    def setup_ui(self):
        """设置UI"""
        self.setWindowTitle("选择通道")
        self.setModal(True)
        self.resize(300, 150)
        
        layout = QVBoxLayout()
        
        # 通道选择
        channel_layout = QHBoxLayout()
        channel_layout.addWidget(QLabel("选择通道:"))
        
        self.channel_combo = QComboBox()
        if len(self.image_shape) == 3:
            # 彩色图像
            for i in range(self.image_shape[2]):
                self.channel_combo.addItem(f"通道 {i}")
        else:
            # 灰度图像
            self.channel_combo.addItem("灰度通道")
        
        channel_layout.addWidget(self.channel_combo)
        layout.addLayout(channel_layout)
        
        # 按钮
        button_layout = QHBoxLayout()
        self.btn_ok = QPushButton("确定")
        self.btn_cancel = QPushButton("取消")
        
        self.btn_ok.clicked.connect(self.accept)
        self.btn_cancel.clicked.connect(self.reject)
        
        button_layout.addWidget(self.btn_ok)
        button_layout.addWidget(self.btn_cancel)
        layout.addLayout(button_layout)
        
        self.setLayout(layout)
    
    def get_selected_channel(self):
        """获取选中的通道"""
        return self.channel_combo.currentIndex()


class VTKDepthViewer(QDialog):
    """VTK 3D深度图查看器"""
    
    def __init__(self, depth_data, channel_name="深度图", parent=None):
        super().__init__(parent)
        self.depth_data = depth_data
        self.channel_name = channel_name
        self.setup_ui()
    
    def setup_ui(self):
        """设置UI"""
        self.setWindowTitle(f"3D深度视图 - {self.channel_name}")
        self.resize(800, 600)
        
        layout = QVBoxLayout()
        
        if not VTK_AVAILABLE:
            error_label = QLabel("VTK未安装！\n请运行: pip install vtk")
            error_label.setAlignment(Qt.AlignCenter)
            layout.addWidget(error_label)
            self.setLayout(layout)
            return
        
        # VTK渲染窗口
        self.vtk_widget = QVTKRenderWindowInteractor(self)
        layout.addWidget(self.vtk_widget)
        
        # 控制面板
        control_layout = QHBoxLayout()
        
        # 高度缩放控制
        control_layout.addWidget(QLabel("高度缩放:"))
        self.height_slider = QSlider(Qt.Horizontal)
        self.height_slider.setRange(1, 100)
        self.height_slider.setValue(10)
        self.height_slider.valueChanged.connect(self.update_height_scale)
        control_layout.addWidget(self.height_slider)
        
        self.height_label = QLabel("10x")
        control_layout.addWidget(self.height_label)
        
        # 颜色映射选择
        control_layout.addWidget(QLabel("颜色映射:"))
        self.colormap_combo = QComboBox()
        colormaps = ["Jet", "Hot", "Cool", "Rainbow", "Viridis", "Gray"]
        self.colormap_combo.addItems(colormaps)
        self.colormap_combo.currentTextChanged.connect(self.update_colormap)
        control_layout.addWidget(self.colormap_combo)
        
        # 重置视角按钮
        reset_btn = QPushButton("重置视角")
        reset_btn.clicked.connect(self.reset_camera)
        control_layout.addWidget(reset_btn)
        
        layout.addLayout(control_layout)
        self.setLayout(layout)
        
        # 创建3D场景
        self.create_3d_scene()
    
    def create_3d_scene(self):
        """创建3D场景"""
        if not VTK_AVAILABLE:
            return
            
        # 创建渲染器
        self.renderer = vtk.vtkRenderer()
        self.vtk_widget.GetRenderWindow().AddRenderer(self.renderer)
        self.interactor = self.vtk_widget.GetRenderWindow().GetInteractor()
        
        # 创建深度表面
        self.create_depth_surface()
        
        # 设置背景
        self.renderer.SetBackground(0.1, 0.2, 0.4)
        
        # 添加坐标轴
        self.add_axes()
        
        # 重置相机
        self.reset_camera()
        
        # 启用交互
        self.interactor.Initialize()
    
    def create_depth_surface(self):
        """创建深度表面"""
        h, w = self.depth_data.shape
        
        # 创建点云
        points = vtk.vtkPoints()
        
        # 标准化深度数据
        depth_normalized = cv2.normalize(self.depth_data, None, 0, 1, cv2.NORM_MINMAX, cv2.CV_32F)
        
        # 创建点和标量数据
        scalars = vtk.vtkFloatArray()
        scalars.SetName("Depth")
        
        for y in range(h):
            for x in range(w):
                z = depth_normalized[y, x] * 10  # 初始高度缩放
                points.InsertNextPoint(x, y, z)
                scalars.InsertNextValue(self.depth_data[y, x])
        
        # 创建结构化网格
        self.structured_grid = vtk.vtkStructuredGrid()
        self.structured_grid.SetDimensions(w, h, 1)
        self.structured_grid.SetPoints(points)
        self.structured_grid.GetPointData().SetScalars(scalars)
        
        # 创建几何过滤器
        geometry_filter = vtk.vtkStructuredGridGeometryFilter()
        geometry_filter.SetInputData(self.structured_grid)
        
        # 创建映射器
        self.mapper = vtk.vtkPolyDataMapper()
        self.mapper.SetInputConnection(geometry_filter.GetOutputPort())
        self.mapper.SetScalarRange(self.depth_data.min(), self.depth_data.max())
        
        # 设置颜色映射
        self.update_colormap("Jet")
        
        # 创建演员
        self.actor = vtk.vtkActor()
        self.actor.SetMapper(self.mapper)
        
        # 添加到渲染器
        self.renderer.AddActor(self.actor)
    
    def update_height_scale(self, value):
        """更新高度缩放"""
        self.height_label.setText(f"{value}x")
        
        # 重新设置点的Z坐标
        h, w = self.depth_data.shape
        depth_normalized = cv2.normalize(self.depth_data, None, 0, 1, cv2.NORM_MINMAX, cv2.CV_32F)
        
        points = self.structured_grid.GetPoints()
        for y in range(h):
            for x in range(w):
                point_id = y * w + x
                current_point = points.GetPoint(point_id)
                z = depth_normalized[y, x] * value
                points.SetPoint(point_id, current_point[0], current_point[1], z)
        
        points.Modified()
        self.vtk_widget.GetRenderWindow().Render()
    
    def update_colormap(self, colormap_name):
        """更新颜色映射"""
        lut = vtk.vtkLookupTable()
        lut.SetNumberOfColors(256)
        
        if colormap_name == "Jet":
            lut.SetHueRange(0.667, 0.0)
        elif colormap_name == "Hot":
            lut.SetHueRange(0.0, 0.167)
        elif colormap_name == "Cool":
            lut.SetHueRange(0.667, 1.0)
        elif colormap_name == "Rainbow":
            lut.SetHueRange(0.0, 0.667)
        elif colormap_name == "Viridis":
            lut.SetHueRange(0.25, 0.75)
        elif colormap_name == "Gray":
            lut.SetHueRange(0.0, 0.0)
            lut.SetSaturationRange(0.0, 0.0)
        
        lut.SetRange(self.depth_data.min(), self.depth_data.max())
        lut.Build()
        
        self.mapper.SetLookupTable(lut)
        self.vtk_widget.GetRenderWindow().Render()
    
    def add_axes(self):
        """添加坐标轴"""
        axes = vtk.vtkAxesActor()
        axes.SetTotalLength(50, 50, 50)
        
        # 创建方向小窗口
        self.axes_widget = vtk.vtkOrientationMarkerWidget()
        self.axes_widget.SetOrientationMarker(axes)
        self.axes_widget.SetInteractor(self.interactor)
        self.axes_widget.SetViewport(0.0, 0.0, 0.2, 0.2)
        self.axes_widget.SetEnabled(1)
        self.axes_widget.InteractiveOn()
    
    def reset_camera(self):
        """重置相机视角"""
        self.renderer.ResetCamera()
        camera = self.renderer.GetActiveCamera()
        camera.SetPosition(0, 0, 100)
        camera.SetFocalPoint(self.depth_data.shape[1]/2, self.depth_data.shape[0]/2, 0)
        camera.SetViewUp(0, -1, 0)  # Y轴向下
        self.renderer.SetActiveCamera(camera)
        self.vtk_widget.GetRenderWindow().Render()


class ResizeHandle(QGraphicsRectItem):
    """缩放句柄类"""
    
    def __init__(self, parent_item, handle_type, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parent_item = parent_item
        self.handle_type = handle_type  # 'tl', 'tr', 'bl', 'br', 't', 'b', 'l', 'r'
        self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsGeometryChanges)
        self.setCursor(self.get_cursor_for_handle())
        
    def get_cursor_for_handle(self):
        """根据句柄类型返回相应的鼠标光标"""
        cursor_map = {
            'tl': Qt.SizeFDiagCursor,    # 左上
            'tr': Qt.SizeBDiagCursor,    # 右上
            'bl': Qt.SizeBDiagCursor,    # 左下
            'br': Qt.SizeFDiagCursor,    # 右下
            't': Qt.SizeVerCursor,       # 上中
            'b': Qt.SizeVerCursor,       # 下中
            'l': Qt.SizeHorCursor,       # 左中
            'r': Qt.SizeHorCursor,       # 右中
        }
        return cursor_map.get(self.handle_type, Qt.ArrowCursor)
    
    def mousePressEvent(self, event):
        """鼠标按下事件"""
        if event.button() == Qt.LeftButton:
            self.parent_item.start_resize(self, event.scenePos())
            event.accept()
        else:
            super().mousePressEvent(event)
    
    def mouseMoveEvent(self, event):
        """鼠标移动事件"""
        if self.parent_item.resizing and self.parent_item.resize_handle == self:
            self.parent_item.resize_to(event.scenePos())
            event.accept()
        else:
            super().mouseMoveEvent(event)
    
    def mouseReleaseEvent(self, event):
        """鼠标释放事件"""
        if event.button() == Qt.LeftButton and self.parent_item.resizing:
            self.parent_item.end_resize()
            event.accept()
        else:
            super().mouseReleaseEvent(event)


class ResizableRectItem(QGraphicsRectItem):
    """可拖拽、缩放的矩形AOI项"""
    
    def __init__(self, rect, aoi_data, scale_factor=1.0, parent_viewer=None):
        super().__init__(rect)
        self.aoi_data = aoi_data
        self.scale_factor = scale_factor
        self.parent_viewer = parent_viewer  # 保存父视图的引用
        self.setFlags(QGraphicsItem.ItemIsMovable |
                      QGraphicsItem.ItemIsSelectable |
                      QGraphicsItem.ItemSendsGeometryChanges)
        
        # 初始化句柄列表和缩放相关变量
        self.handles = []
        self.handle_size = 8
        self.resizing = False
        self.resize_handle = None
        self.resize_start_pos = None
        self.original_rect = None
        
        # 设置外观
        self.update_pen_width()
        self.setBrush(QBrush(QColor(aoi_data.get('color', 'red')), Qt.NoBrush))
        
        # 创建调整大小的句柄
        self.create_handles()
    
    def update_pen_width(self):
        """根据缩放比例更新线条宽度"""
        # 基础线条宽度
        base_width = 2.0
        # 根据缩放比例调整，确保线条在任何缩放级别都可见
        pen_width = max(1.0, base_width / self.scale_factor)
        
        pen = QPen(QColor(self.aoi_data.get('color', 'red')), pen_width)
        self.setPen(pen)
        
        # 同时更新句柄的线条宽度
        handle_pen_width = max(0.5, 1.0 / self.scale_factor)
        for handle in self.handles:
            handle.setPen(QPen(Qt.black, handle_pen_width))
    
    def update_color(self, color_name):
        """更新AOI颜色"""
        self.aoi_data['color'] = color_name
        self.update_pen_width()  # 重新设置颜色和线条宽度
    
    def update_scale_factor(self, scale_factor):
        """更新缩放比例并调整显示"""
        self.scale_factor = scale_factor
        self.update_pen_width()
        self.update_handle_size()
        
    def create_handles(self):
        """创建调整大小的句柄"""
        rect = self.rect()
        handle_size = self.get_scaled_handle_size()
        handle_pen_width = max(0.5, 1.0 / self.scale_factor)
        
        # 句柄位置和类型
        handle_data = [
            (rect.left(), rect.top(), 'tl'),          # 左上
            (rect.right(), rect.top(), 'tr'),         # 右上
            (rect.left(), rect.bottom(), 'bl'),       # 左下
            (rect.right(), rect.bottom(), 'br'),      # 右下
            (rect.center().x(), rect.top(), 't'),     # 上中
            (rect.center().x(), rect.bottom(), 'b'),  # 下中
            (rect.left(), rect.center().y(), 'l'),    # 左中
            (rect.right(), rect.center().y(), 'r'),   # 右中
        ]
        
        for x, y, handle_type in handle_data:
            handle = ResizeHandle(
                self, handle_type,
                x - handle_size/2,
                y - handle_size/2,
                handle_size,
                handle_size
            )
            handle.setParentItem(self)
            handle.setBrush(QBrush(Qt.white))
            handle.setPen(QPen(Qt.black, handle_pen_width))
            self.handles.append(handle)
    
    def update_handles(self):
        """更新句柄位置"""
        rect = self.rect()
        handle_size = self.get_scaled_handle_size()
        
        # 句柄位置和类型对应关系
        handle_positions = [
            (rect.left(), rect.top()),          # 左上 (tl)
            (rect.right(), rect.top()),         # 右上 (tr)
            (rect.left(), rect.bottom()),       # 左下 (bl)
            (rect.right(), rect.bottom()),      # 右下 (br)
            (rect.center().x(), rect.top()),    # 上中 (t)
            (rect.center().x(), rect.bottom()), # 下中 (b)
            (rect.left(), rect.center().y()),   # 左中 (l)
            (rect.right(), rect.center().y()),  # 右中 (r)
        ]
        
        for handle, pos in zip(self.handles, handle_positions):
            handle.setRect(
                pos[0] - handle_size/2,
                pos[1] - handle_size/2,
                handle_size,
                handle_size
            )
    
    def get_scaled_handle_size(self):
        """获取根据缩放调整的句柄大小"""
        base_size = 8.0
        return max(4.0, base_size / self.scale_factor)
    
    def update_handle_size(self):
        """更新句柄大小"""
        handle_size = self.get_scaled_handle_size()
        rect = self.rect()
        handle_positions = [
            (rect.left(), rect.top()),
            (rect.right(), rect.top()),
            (rect.left(), rect.bottom()),
            (rect.right(), rect.bottom()),
            (rect.center().x(), rect.top()),
            (rect.center().x(), rect.bottom()),
            (rect.left(), rect.center().y()),
            (rect.right(), rect.center().y()),
        ]
        
        for handle, pos in zip(self.handles, handle_positions):
            handle.setRect(
                pos[0] - handle_size/2,
                pos[1] - handle_size/2,
                handle_size,
                handle_size
            )
    
    def start_resize(self, handle, scene_pos):
        """开始缩放操作"""
        self.resizing = True
        self.resize_handle = handle
        self.resize_start_pos = scene_pos
        self.original_rect = self.rect()
        # 禁用移动功能，避免与缩放冲突
        self.setFlag(QGraphicsItem.ItemIsMovable, False)
    
    def resize_to(self, scene_pos):
        """执行缩放"""
        if not self.resizing or not self.resize_handle:
            return
        
        # 将场景坐标转换为本地坐标
        local_pos = self.mapFromScene(scene_pos)
        start_local = self.mapFromScene(self.resize_start_pos)
        
        # 计算位移
        dx = local_pos.x() - start_local.x()
        dy = local_pos.y() - start_local.y()
        
        # 获取当前矩形
        rect = self.original_rect
        new_rect = QRectF(rect)
        
        # 根据句柄类型调整矩形
        handle_type = self.resize_handle.handle_type
        
        if handle_type == 'tl':  # 左上
            new_rect.setTopLeft(rect.topLeft() + QPointF(dx, dy))
        elif handle_type == 'tr':  # 右上
            new_rect.setTopRight(rect.topRight() + QPointF(dx, dy))
        elif handle_type == 'bl':  # 左下
            new_rect.setBottomLeft(rect.bottomLeft() + QPointF(dx, dy))
        elif handle_type == 'br':  # 右下
            new_rect.setBottomRight(rect.bottomRight() + QPointF(dx, dy))
        elif handle_type == 't':  # 上中
            new_rect.setTop(rect.top() + dy)
        elif handle_type == 'b':  # 下中
            new_rect.setBottom(rect.bottom() + dy)
        elif handle_type == 'l':  # 左中
            new_rect.setLeft(rect.left() + dx)
        elif handle_type == 'r':  # 右中
            new_rect.setRight(rect.right() + dx)
        
        # 确保最小尺寸
        min_size = 10
        if new_rect.width() < min_size:
            if handle_type in ['tl', 'bl', 'l']:
                new_rect.setLeft(new_rect.right() - min_size)
            else:
                new_rect.setRight(new_rect.left() + min_size)
        
        if new_rect.height() < min_size:
            if handle_type in ['tl', 'tr', 't']:
                new_rect.setTop(new_rect.bottom() - min_size)
            else:
                new_rect.setBottom(new_rect.top() + min_size)
        
        # 应用新的矩形
        self.setRect(new_rect)
        self.update_handles()
        self.update_aoi_data()
    
    def end_resize(self):
        """结束缩放操作"""
        self.resizing = False
        self.resize_handle = None
        self.resize_start_pos = None
        self.original_rect = None
        # 重新启用移动功能
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
    
    def itemChange(self, change, value):
        """监听项的变化"""
        if change == QGraphicsRectItem.ItemPositionHasChanged:
            self.update_aoi_data()
        elif change == QGraphicsRectItem.ItemSelectedHasChanged:
            for handle in self.handles:
                handle.setVisible(value)
            # 当AOI被选中时，通知主窗口
            if value and self.parent_viewer:
                self.parent_viewer.aoi_selected.emit(self.aoi_data)
        return super().itemChange(change, value)
    
    def update_aoi_data(self):
        """更新AOI数据"""
        rect = self.rect()
        pos = self.pos()
        old_data = self.aoi_data.copy()  # 保存旧数据用于比较
        
        self.aoi_data['x'] = int(pos.x() + rect.x())
        self.aoi_data['y'] = int(pos.y() + rect.y())
        self.aoi_data['width'] = int(rect.width())
        self.aoi_data['height'] = int(rect.height())
        
        # 检查数据是否真的发生了变化
        if (old_data['x'] != self.aoi_data['x'] or
                old_data['y'] != self.aoi_data['y'] or
                old_data['width'] != self.aoi_data['width'] or
                old_data['height'] != self.aoi_data['height']):
            # 通过父视图发射信号通知数据变化
            if self.parent_viewer:
                self.parent_viewer.aoi_data_changed.emit(self.aoi_data)


class ImageViewer(QGraphicsView):
    """自定义图像查看器，支持缩放和AOI创建"""
    
    aoi_created = pyqtSignal(dict)  # AOI创建信号
    aoi_selected = pyqtSignal(dict)  # AOI选择信号
    aoi_data_changed = pyqtSignal(dict)  # AOI数据变化信号
    mouse_moved = pyqtSignal(int, int)  # 鼠标移动信号
    scale_changed = pyqtSignal(int)  # 缩放变化信号
    
    def __init__(self):
        super().__init__()
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        
        # 图像相关
        self.image_item = None
        self.original_pixmap = None
        self.scale_factor = 1.0
        
        # AOI创建相关
        self.creating_aoi = False
        self.start_point = None
        self.current_rect_item = None
        self.aoi_items = []
        self.aoi_counter = 1
        
        # 设置视图属性
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        self.setRenderHint(QPainter.Antialiasing)
        
        # 禁用滚动条的滚轮响应，启用自定义缩放
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        
        # 设置缩放锚点为鼠标位置
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        
        # 启用鼠标跟踪
        self.setMouseTracking(True)
        

        
    def set_image(self, pixmap):
        """设置要显示的图像"""
        self.scene.clear()
        self.aoi_items.clear()
        
        self.original_pixmap = pixmap
        self.image_item = QGraphicsPixmapItem(pixmap)
        self.scene.addItem(self.image_item)
        self.scene.setSceneRect(QRectF(pixmap.rect()))
        
    def wheelEvent(self, event):
        """鼠标滚轮缩放"""
        # 只处理缩放，不传递给滚动条
        if self.original_pixmap and hasattr(event, 'angleDelta'):
            # 获取滚轮方向
            delta = event.angleDelta().y()
            
            # 计算缩放因子
            if delta > 0:
                factor = 1.15  # 放大，调整为更平滑的缩放
            else:
                factor = 0.87  # 缩小
            
            # 扩大缩放范围：1%到5000%
            new_scale = self.scale_factor * factor
            
            if 0.01 <= new_scale <= 50.0:
                self.scale(factor, factor)
                self.scale_factor = new_scale
                
                # 更新所有AOI项的线条宽度
                self.update_aoi_display()
                
                # 发射缩放变化信号
                self.scale_changed.emit(int(self.scale_factor * 100))
            
            event.accept()
        else:
            super().wheelEvent(event)
    
    def mousePressEvent(self, event: QMouseEvent):
        """鼠标按下事件"""
        if event.button() == Qt.LeftButton and self.creating_aoi:
            # 开始创建AOI
            scene_pos = self.mapToScene(event.pos())
            self.start_point = scene_pos
            event.accept()
        else:
            super().mousePressEvent(event)
    
    def mouseMoveEvent(self, event: QMouseEvent):
        """鼠标移动事件"""
        scene_pos = self.mapToScene(event.pos())
        
        # 发射鼠标位置信号
        if self.image_item and self.image_item.contains(scene_pos):
            self.mouse_moved.emit(int(scene_pos.x()), int(scene_pos.y()))
        
        if self.creating_aoi and self.start_point:
            # 创建临时矩形显示
            if self.current_rect_item:
                self.scene.removeItem(self.current_rect_item)
            
            rect = QRectF(self.start_point, scene_pos).normalized()
            self.current_rect_item = self.scene.addRect(
                rect, QPen(Qt.red, 2, Qt.DashLine))
            event.accept()
        else:
            super().mouseMoveEvent(event)
    
    def mouseReleaseEvent(self, event: QMouseEvent):
        """鼠标释放事件"""
        if event.button() == Qt.LeftButton and self.creating_aoi and self.start_point:
            # 完成AOI创建
            end_point = self.mapToScene(event.pos())
            rect = QRectF(self.start_point, end_point).normalized()
            
            if rect.width() > 10 and rect.height() > 10:  # 最小尺寸检查
                self.create_aoi(rect)
            
            # 清理临时对象
            if self.current_rect_item:
                self.scene.removeItem(self.current_rect_item)
                self.current_rect_item = None
            
            self.start_point = None
            self.creating_aoi = False
            self.setCursor(Qt.ArrowCursor)
            # 重新启用拖动
            self.setDragMode(QGraphicsView.ScrollHandDrag)
            event.accept()
        else:
            super().mouseReleaseEvent(event)
    
    def create_aoi(self, rect):
        """创建AOI区域"""
        aoi_data = {
            'id': self.aoi_counter,
            'name': f'AOI_{self.aoi_counter}',
            'x': int(rect.x()),
            'y': int(rect.y()),
            'width': int(rect.width()),
            'height': int(rect.height()),
            'color': 'red',
            'description': ''
        }
        
        aoi_item = ResizableRectItem(
            QRectF(0, 0, rect.width(), rect.height()),
            aoi_data, self.scale_factor, self)
        aoi_item.setPos(rect.x(), rect.y())
        
        # 设置tooltip为AOI名称
        aoi_item.setToolTip(aoi_data['name'])
        
        self.scene.addItem(aoi_item)
        self.aoi_items.append(aoi_item)
        
        self.aoi_counter += 1
        self.aoi_created.emit(aoi_data)
    
    def start_aoi_creation(self):
        """开始AOI创建模式"""
        self.creating_aoi = True
        self.setCursor(Qt.CrossCursor)
        # 在AOI创建模式下禁用拖动
        self.setDragMode(QGraphicsView.NoDrag)
    
    def update_aoi_display(self):
        """更新所有AOI项的显示（线条宽度、句柄大小等）"""
        for aoi_item in self.aoi_items:
            aoi_item.update_scale_factor(self.scale_factor)
    
    def zoom_to_fit(self):
        """缩放以适应窗口"""
        if self.image_item:
            self.fitInView(self.image_item, Qt.KeepAspectRatio)
            
            # 重新计算实际缩放比例
            # 考虑当前的变换矩阵
            transform = self.transform()
            current_scale = transform.m11()  # 获取X轴缩放比例
            
            self.scale_factor = current_scale
            
            # 更新所有AOI项的显示
            self.update_aoi_display()
            
            self.scale_changed.emit(int(self.scale_factor * 100))
    
    def zoom_to_actual(self):
        """缩放到实际大小"""
        if self.image_item:
            self.resetTransform()
            self.scale_factor = 1.0
            
            # 更新所有AOI项的显示
            self.update_aoi_display()
            
            self.scale_changed.emit(100)


class ImageAOIEditor(QMainWindow):
    """图像AOI编辑器主窗口"""
    
    def __init__(self):
        super().__init__()
        
        # 加载UI文件
        ui_path = os.path.join(os.path.dirname(__file__), 'image_aoi_editor.ui')
        uic.loadUi(ui_path, self)
        
        # 初始化变量
        self.current_image_path = None
        self.current_image_array = None  # 保存图像数组用于像素值获取
        self.aoi_list = []
        self.current_aoi = None
        self.updating_properties = False  # 添加标志防止循环更新
        
        # 创建自定义图像查看器
        self.image_viewer = ImageViewer()
        
        # 替换scrollArea中的widget
        self.scrollArea.setWidget(self.image_viewer)
        self.scrollArea.setWidgetResizable(True)
        
        # 设置快捷键
        from PyQt5.QtWidgets import QShortcut
        from PyQt5.QtGui import QKeySequence
        self.toggle_shortcut = QShortcut(QKeySequence("F2"), self)
        self.toggle_shortcut.activated.connect(self.toggle_aoi_panel)
        
        # 连接信号和槽
        self.setup_connections()
        
        # 初始化界面
        self.setup_ui()
    
    def closeEvent(self, event):
        """窗口关闭事件，清理内存"""
        self.cleanup_resources()
        event.accept()
    
    def cleanup_resources(self):
        """清理资源，释放内存"""
        # 清理图像数据
        if hasattr(self, 'current_image_array'):
            self.current_image_array = None
        
        # 清理图像查看器中的图像
        if hasattr(self, 'image_viewer') and self.image_viewer:
            if self.image_viewer.original_pixmap:
                self.image_viewer.original_pixmap = None
            if self.image_viewer.image_item:
                self.image_viewer.scene.removeItem(self.image_viewer.image_item)
                self.image_viewer.image_item = None
            
            # 清理AOI项
            for item in self.image_viewer.aoi_items[:]:
                self.image_viewer.scene.removeItem(item)
            self.image_viewer.aoi_items.clear()
            
            # 清理场景
            self.image_viewer.scene.clear()
        
        # 清理AOI列表
        self.aoi_list.clear()
        self.current_aoi = None
        
        # 强制垃圾回收
        import gc
        gc.collect()
    
    def rotate_image_left(self):
        """逆时针旋转图像90度"""
        if self.current_image_array is None:
            QMessageBox.warning(self, '警告', '没有图像可旋转')
            return
        
        try:
            # 记录原始数据类型
            original_dtype = self.current_image_array.dtype
            print(f"旋转前: 数据类型={original_dtype}, 形状={self.current_image_array.shape}")
            
            # 旋转图像（cv2.rotate 支持各种数据类型）
            self.current_image_array = cv2.rotate(self.current_image_array, cv2.ROTATE_90_COUNTERCLOCKWISE)
            
            print(f"旋转后: 数据类型={self.current_image_array.dtype}, 形状={self.current_image_array.shape}")
            
            # 更新显示
            self.update_image_display()
            
            # 更新AOI坐标
            self.transform_aois_for_rotation(-90)
            
        except Exception as e:
            QMessageBox.critical(self, '错误', f'图像旋转失败:\n{str(e)}')
            print(f"旋转错误: {e}")
        
    def rotate_image_right(self):
        """顺时针旋转图像90度"""
        if self.current_image_array is None:
            QMessageBox.warning(self, '警告', '没有图像可旋转')
            return
        
        try:
            # 记录原始数据类型
            original_dtype = self.current_image_array.dtype
            print(f"旋转前: 数据类型={original_dtype}, 形状={self.current_image_array.shape}")
            
            # 旋转图像（cv2.rotate 支持各种数据类型）
            self.current_image_array = cv2.rotate(self.current_image_array, cv2.ROTATE_90_CLOCKWISE)
            
            print(f"旋转后: 数据类型={self.current_image_array.dtype}, 形状={self.current_image_array.shape}")
            
            # 更新显示
            self.update_image_display()
            
            # 更新AOI坐标
            self.transform_aois_for_rotation(90)
            
        except Exception as e:
            QMessageBox.critical(self, '错误', f'图像旋转失败:\n{str(e)}')
            print(f"旋转错误: {e}")
        
    def flip_image_horizontal(self):
        """水平翻转图像"""
        if self.current_image_array is None:
            QMessageBox.warning(self, '警告', '没有图像可翻转')
            return
        
        try:
            # 记录原始数据类型
            original_dtype = self.current_image_array.dtype
            print(f"翻转前: 数据类型={original_dtype}, 形状={self.current_image_array.shape}")
            
            # 水平翻转图像（cv2.flip 支持各种数据类型）
            self.current_image_array = cv2.flip(self.current_image_array, 1)
            
            print(f"翻转后: 数据类型={self.current_image_array.dtype}, 形状={self.current_image_array.shape}")
            
            # 更新显示
            self.update_image_display()
            
            # 更新AOI坐标
            self.transform_aois_for_flip('horizontal')
            
        except Exception as e:
            QMessageBox.critical(self, '错误', f'图像翻转失败:\n{str(e)}')
            print(f"翻转错误: {e}")
        
    def flip_image_vertical(self):
        """垂直翻转图像"""
        if self.current_image_array is None:
            QMessageBox.warning(self, '警告', '没有图像可翻转')
            return
        
        try:
            # 记录原始数据类型
            original_dtype = self.current_image_array.dtype
            print(f"翻转前: 数据类型={original_dtype}, 形状={self.current_image_array.shape}")
            
            # 垂直翻转图像（cv2.flip 支持各种数据类型）
            self.current_image_array = cv2.flip(self.current_image_array, 0)
            
            print(f"翻转后: 数据类型={self.current_image_array.dtype}, 形状={self.current_image_array.shape}")
            
            # 更新显示
            self.update_image_display()
            
            # 更新AOI坐标
            self.transform_aois_for_flip('vertical')
            
        except Exception as e:
            QMessageBox.critical(self, '错误', f'图像翻转失败:\n{str(e)}')
            print(f"翻转错误: {e}")
    
    def update_image_display(self):
        """更新图像显示"""
        if self.current_image_array is None:
            return
            
        # 保存当前图像尺寸为下一次变换做准备
        h, w = self.current_image_array.shape[:2]
        self.previous_image_size = (h, w)
            
        # 标准化图像用于显示（处理各种数据类型）
        display_image = self.normalize_image_for_display(self.current_image_array)
        
        # 转换为Qt格式
        img_rgb = cv2.cvtColor(display_image, cv2.COLOR_BGR2RGB)
        h, w, ch = img_rgb.shape
        bytes_per_line = ch * w
        qt_image = QImage(img_rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)
        pixmap = QPixmap.fromImage(qt_image)
        
        # 更新图像查看器
        self.image_viewer.set_image(pixmap)
        
        # 重新创建AOI图形项
        self.recreate_aoi_items()
        
        # 更新界面信息
        original_dtype = self.current_image_array.dtype
        min_val = self.current_image_array.min()
        max_val = self.current_image_array.max()
        self.label_image_info.setText(
            f'图像信息: {w}x{h}, {original_dtype}, 范围:[{min_val:.2f}, {max_val:.2f}], '
            f'{os.path.basename(self.current_image_path) if self.current_image_path else "未知"}'
        )
        
        # 适应窗口大小
        self.fit_to_window()
        
        # 清理临时变量
        del img_rgb, qt_image
    
    def transform_aois_for_rotation(self, angle):
        """根据旋转角度变换AOI坐标"""
        if not self.aoi_list:
            return
            
        # 获取图像尺寸（变换前）
        if hasattr(self, 'previous_image_size'):
            prev_h, prev_w = self.previous_image_size
        else:
            # 如果没有保存之前的尺寸，根据旋转角度推断
            curr_h, curr_w = self.current_image_array.shape[:2]
            if abs(angle) == 90:
                prev_h, prev_w = curr_w, curr_h
            else:
                prev_h, prev_w = curr_h, curr_w
        
        for aoi in self.aoi_list:
            x, y, w, h = aoi['x'], aoi['y'], aoi['width'], aoi['height']
            
            if angle == 90:  # 顺时针旋转90度
                new_x = prev_h - y - h
                new_y = x
                new_w, new_h = h, w
            elif angle == -90:  # 逆时针旋转90度
                new_x = y
                new_y = prev_w - x - w
                new_w, new_h = h, w
            elif angle == 180:  # 旋转180度
                new_x = prev_w - x - w
                new_y = prev_h - y - h
                new_w, new_h = w, h
            else:
                continue
                
            aoi['x'] = max(0, new_x)
            aoi['y'] = max(0, new_y)
            aoi['width'] = new_w
            aoi['height'] = new_h
        
        # 更新AOI树显示
        self.update_aoi_tree()
    
    def transform_aois_for_flip(self, flip_type):
        """根据翻转类型变换AOI坐标"""
        if not self.aoi_list:
            return
            
        h, w = self.current_image_array.shape[:2]
        
        for aoi in self.aoi_list:
            x, y, width, height = aoi['x'], aoi['y'], aoi['width'], aoi['height']
            
            if flip_type == 'horizontal':
                # 水平翻转：x坐标变换
                new_x = w - x - width
                aoi['x'] = max(0, new_x)
            elif flip_type == 'vertical':
                # 垂直翻转：y坐标变换
                new_y = h - y - height
                aoi['y'] = max(0, new_y)
        
        # 更新AOI树显示
        self.update_aoi_tree()
    
    def show_depth_3d(self):
        """显示3D深度图"""
        if self.current_image_array is None:
            QMessageBox.warning(self, '警告', '没有图像可显示')
            return
        
        if not VTK_AVAILABLE:
            QMessageBox.warning(self, '警告', 
                              'VTK未安装！\n请运行命令安装：pip install vtk')
            return
        
        # 如果是单通道图像，直接显示
        if len(self.current_image_array.shape) == 2:
            depth_data = self.current_image_array
            channel_name = "灰度通道"
            self.display_depth_viewer(depth_data, channel_name)
            return
        
        # 多通道图像，让用户选择通道
        channel_selector = ChannelSelector(self.current_image_array.shape, self)
        if channel_selector.exec_() == QDialog.Accepted:
            selected_channel = channel_selector.get_selected_channel()
            
            # 提取选中的通道
            if len(self.current_image_array.shape) == 3:
                depth_data = self.current_image_array[:, :, selected_channel]
                channel_name = f"通道 {selected_channel}"
            else:
                depth_data = self.current_image_array
                channel_name = "灰度通道"
            
            self.display_depth_viewer(depth_data, channel_name)
    
    def display_depth_viewer(self, depth_data, channel_name):
        """显示VTK深度图查看器"""
        try:
            # 检查数据类型，如果不是数值类型，进行转换
            if depth_data.dtype == np.uint8:
                # uint8转换为float32以获得更好的深度效果
                depth_data = depth_data.astype(np.float32)
            elif depth_data.dtype == np.uint16:
                # uint16转换为float32并标准化
                depth_data = depth_data.astype(np.float32)
            
            # 确保深度数据是连续的
            depth_data = np.ascontiguousarray(depth_data)
            
            # 创建并显示VTK查看器
            vtk_viewer = VTKDepthViewer(depth_data, channel_name, self)
            vtk_viewer.show()
            
            # 保存引用，防止被垃圾回收
            if not hasattr(self, 'vtk_viewers'):
                self.vtk_viewers = []
            self.vtk_viewers.append(vtk_viewer)
            
        except Exception as e:
            QMessageBox.critical(self, '错误', f'创建3D深度图失败：\n{str(e)}')
    
    def setup_connections(self):
        """设置信号和槽连接"""
        # 按钮连接
        self.btn_open_image.clicked.connect(self.open_image)
        self.btn_add_aoi.clicked.connect(self.add_aoi)
        self.btn_delete_aoi.clicked.connect(self.delete_aoi)
        self.btn_clear_all.clicked.connect(self.clear_all_aoi)
        self.btn_save_aoi.clicked.connect(self.save_aoi_data)
        self.btn_load_aoi.clicked.connect(self.load_aoi_data)
        self.btn_fit_to_window.clicked.connect(self.fit_to_window)
        self.btn_actual_size.clicked.connect(self.actual_size)
        self.btn_rotate_left.clicked.connect(self.rotate_image_left)
        self.btn_rotate_right.clicked.connect(self.rotate_image_right)
        self.btn_flip_horizontal.clicked.connect(self.flip_image_horizontal)
        self.btn_flip_vertical.clicked.connect(self.flip_image_vertical)
        self.btn_show_depth.clicked.connect(self.show_depth_3d)
        self.btn_color.clicked.connect(self.choose_color)
        self.btn_apply_changes.clicked.connect(self.apply_changes)
        
        # 图像查看器连接
        self.image_viewer.aoi_created.connect(self.on_aoi_created)
        self.image_viewer.aoi_selected.connect(self.on_aoi_selected_from_image)
        self.image_viewer.aoi_data_changed.connect(self.on_aoi_data_changed)
        self.image_viewer.mouse_moved.connect(self.on_mouse_moved)
        self.image_viewer.scale_changed.connect(self.on_scale_changed)
        
        # 树形控件连接
        self.tree_aoi_list.itemClicked.connect(self.on_aoi_selected)
        # 设置右键菜单
        self.tree_aoi_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_aoi_list.customContextMenuRequested.connect(self.show_aoi_context_menu)
        
        # 属性编辑连接
        self.lineEdit_name.textChanged.connect(self.on_property_changed)
        self.spinBox_x.valueChanged.connect(self.on_property_changed)
        self.spinBox_y.valueChanged.connect(self.on_property_changed)
        self.spinBox_width.valueChanged.connect(self.on_property_changed)
        self.spinBox_height.valueChanged.connect(self.on_property_changed)
        self.textEdit_description.textChanged.connect(self.on_property_changed)
        
        # 菜单动作连接
        self.action_open_image.triggered.connect(self.open_image)
        self.action_save_project.triggered.connect(self.save_aoi_data)
        self.action_load_project.triggered.connect(self.load_aoi_data)
        self.action_add_aoi.triggered.connect(self.add_aoi)
        self.action_delete_aoi.triggered.connect(self.delete_aoi)
        self.action_clear_all.triggered.connect(self.clear_all_aoi)
        self.action_fit_to_window.triggered.connect(self.fit_to_window)
        self.action_actual_size.triggered.connect(self.actual_size)
        self.action_rotate_left.triggered.connect(self.rotate_image_left)
        self.action_rotate_right.triggered.connect(self.rotate_image_right)
        self.action_flip_horizontal.triggered.connect(self.flip_image_horizontal)
        self.action_flip_vertical.triggered.connect(self.flip_image_vertical)
        self.action_show_depth.triggered.connect(self.show_depth_3d)
        self.action_about.triggered.connect(self.show_about)
        self.action_exit.triggered.connect(self.close)
        self.action_save_image.triggered.connect(self.save_image)
    def setup_ui(self):
        """初始化界面设置"""
        # 设置树形控件
        self.tree_aoi_list.setHeaderLabels(['属性', '值'])
        self.tree_aoi_list.setColumnWidth(0, 120)
        
        # 禁用属性编辑区域
        self.groupBox_aoi_properties.setEnabled(False)
        
        # 初始化AOI管理面板状态（默认隐藏，精简显示）
        self.aoi_panel_visible = True  # 先设为True，然后调用hide方法
        self.hide_aoi_panel()  # 默认隐藏AOI管理面板
        
        # 设置状态栏
        #self.statusbar.showMessage('就绪')
    
    def hide_aoi_panel(self):
        """隐藏右侧AOI管理面板"""
        if not self.aoi_panel_visible:
            return
            
        # 隐藏整个右侧AOI管理面板
        if hasattr(self, 'frame_aoi_panel'):
            self.frame_aoi_panel.hide()
        
        # 只隐藏工具栏本身，不影响图像显示区域
        # 尝试找到工具栏的具体组件并隐藏
        toolbar_components = [
            'widget_toolbar', 'frame_toolbar', 'toolBar',
            'horizontalLayout_toolbar'
        ]
        
        for toolbar_name in toolbar_components:
            if hasattr(self, toolbar_name):
                toolbar_widget = getattr(self, toolbar_name)
                if hasattr(toolbar_widget, 'hide'):
                    toolbar_widget.hide()
                    break
        
        # 如果找不到工具栏容器，尝试单独隐藏工具栏按钮
        if not any(hasattr(self, name) for name in toolbar_components):
            toolbar_buttons = [
                'btn_open_image', 'btn_add_aoi', 'btn_delete_aoi', 
                'btn_clear_all', 'btn_save_aoi', 'btn_load_aoi',
                'btn_fit_to_window', 'btn_actual_size'
            ]
            
            for button_name in toolbar_buttons:
                if hasattr(self, button_name):
                    button = getattr(self, button_name)
                    button.hide()
        
        # 备用：查找其他可能的组件名称
        panel_components = [
            'widget_right_panel', 'frame_right_panel', 'widget_aoi_management',
            'frame_aoi_management', 'groupBox_aoi_list', 
            'groupBox_aoi_properties'
        ]
        
        for component_name in panel_components:
            if hasattr(self, component_name):
                component = getattr(self, component_name)
                component.hide()
        
        self.aoi_panel_visible = False
        #self.statusbar.showMessage('AOI管理面板和工具栏已隐藏，图片最大化显示')
    
    def show_aoi_panel(self):
        """显示右侧AOI管理面板"""
        if self.aoi_panel_visible:
            return
            
        # 显示整个右侧AOI管理面板
        if hasattr(self, 'frame_aoi_panel'):
            self.frame_aoi_panel.show()
        
        # 重新显示工具栏
        # 尝试找到工具栏的具体组件并显示
        toolbar_components = [
            'widget_toolbar', 'frame_toolbar', 'toolBar',
            'horizontalLayout_toolbar'
        ]
        
        for toolbar_name in toolbar_components:
            if hasattr(self, toolbar_name):
                toolbar_widget = getattr(self, toolbar_name)
                if hasattr(toolbar_widget, 'show'):
                    toolbar_widget.show()
                    break
        
        # 如果找不到工具栏容器，尝试单独显示工具栏按钮
        if not any(hasattr(self, name) for name in toolbar_components):
            toolbar_buttons = [
                'btn_open_image', 'btn_add_aoi', 'btn_delete_aoi', 
                'btn_clear_all', 'btn_save_aoi', 'btn_load_aoi',
                'btn_fit_to_window', 'btn_actual_size'
            ]
            
            for button_name in toolbar_buttons:
                if hasattr(self, button_name):
                    button = getattr(self, button_name)
                    button.show()
        
        # 备用：查找其他可能的组件名称
        panel_components = [
            'widget_right_panel', 'frame_right_panel', 'widget_aoi_management',
            'frame_aoi_management', 'groupBox_aoi_list', 
            'groupBox_aoi_properties'
        ]
        
        for component_name in panel_components:
            if hasattr(self, component_name):
                component = getattr(self, component_name)
                component.show()
        
        self.aoi_panel_visible = True
        #self.statusbar.showMessage('AOI管理面板和工具栏已显示')

           
    def toggle_aoi_panel(self):
        """切换AOI管理面板的显示/隐藏状态"""
        if self.aoi_panel_visible:
            self.hide_aoi_panel()
        else:
            self.show_aoi_panel()

    def save_image(self):
        """保存图像文件"""
        file_path, _ = QFileDialog.getSaveFileName(
            self, '保存图像文件',
            '', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tiff *.tif)')
        
        if file_path:
            try:
                if self.current_image_array is None:
                    QMessageBox.warning(self, '警告', '没有图像可保存')
                    return
                save_image_chinese_path(self.current_image_array, file_path)
            except Exception as e:
                QMessageBox.critical(self, '错误', f'无法打开图像文件:\n{str(e)}')

    def open_image(self):
        """打开图像文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, '打开图像文件',
            '', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tiff *.tif)')
        
        if file_path:
            try:
                # 先清理之前的图像资源
                self.cleanup_image_resources()
                
                # 使用OpenCV读取图像（支持中文路径）
                img_data = np.fromfile(file_path, dtype=np.uint8)
                
                # 根据文件扩展名选择不同的解码标志
                ext = file_path.lower().split('.')[-1]
                img = None
                if ext in ['exr', 'hdr', 'tiff', 'tif']:
                    # 对于浮点格式，使用ANYDEPTH和ANYCOLOR标志
                    img = cv2.imdecode(img_data, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_ANYCOLOR)
                    print(f"Loaded ex image with shape: {img.shape}, dtype: {img.dtype}")
                else:
                    # 对于普通格式，使用COLOR标志
                    img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
                    print(f"Loaded image with shape: {img.shape}, dtype: {img.dtype}")
            

                if img is None:
                    raise ValueError("无法读取图像文件")
                
                self.open_image_cv(img, window_title=os.path.basename(file_path))
                # # 保存原始图像数组用于像素值获取
                # self.current_image_array = img
                
                # # 保存图像尺寸用于坐标变换
                # h, w = img.shape[:2]
                # self.previous_image_size = (h, w)
                
                # # 转换为Qt格式（临时变量，会自动释放）
                # img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                # h, w, ch = img_rgb.shape
                # bytes_per_line = ch * w
                # qt_image = QImage(img_rgb.data, w, h, bytes_per_line, 
                #                  QImage.Format_RGB888)
                # pixmap = QPixmap.fromImage(qt_image)
                
                # # img_rgb 和 qt_image 在这里会被自动回收
                # del img_rgb, qt_image
                
                # # 设置到图像查看器
                # self.image_viewer.set_image(pixmap)
                # self.current_image_path = file_path
                
                # # 更新界面
                # self.label_image_info.setText(
                #     f'图像信息: {w}x{h}, {os.path.basename(file_path)}')
                
                # # 适应窗口大小
                # self.fit_to_window()
                
                #self.statusbar.showMessage(f'已加载图像: {os.path.basename(file_path)}')
                
            except Exception as e:
                QMessageBox.critical(self, '错误', f'无法打开图像文件:\n{str(e)}')

    def open_image_cv(self, cv_image, window_title='图像显示'):
        """打开图像文件"""

        
        if True:
            try:
                # 先清理之前的图像资源
                self.cleanup_image_resources()
                
                # 使用OpenCV读取图像（支持中文路径）
                #img_data = np.fromfile(file_path, dtype=np.uint8)
                #img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
                img = cv_image
                if img is None:
                    raise ValueError("无法读取图像文件")
                
                # 保存原始图像数组用于像素值获取
                self.current_image_array = img
                img = self.normalize_image_for_display(img)

                # 保存图像尺寸用于坐标变换
                h, w = img.shape[:2]
                self.previous_image_size = (h, w)
                
                # 转换为Qt格式（临时变量，会自动释放）
                img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                h, w, ch = img_rgb.shape
                bytes_per_line = ch * w
                qt_image = QImage(img_rgb.data, w, h, bytes_per_line, 
                                 QImage.Format_RGB888)
                pixmap = QPixmap.fromImage(qt_image)
                
                # img_rgb 和 qt_image 在这里会被自动回收
                del img_rgb, qt_image
                
                # 设置到图像查看器
                self.image_viewer.set_image(pixmap)
                self.current_image_path = file_path
                
                # 更新界面
                self.label_image_info.setText(
                    f'图像信息: {w}x{h}, {os.path.basename(file_path)}')
                
                # 适应窗口大小
                self.fit_to_window()
                
                #self.statusbar.showMessage(f'已加载图像: {os.path.basename(file_path)}')
                
            except Exception as e:
                #QMessageBox.critical(self, '错误', f'无法打开图像文件:\n{str(e)}')
                #print(f"Error opening image: {e}")
                pass

    def cleanup_image_resources(self):
        """清理图像相关资源"""
        # 清理当前图像数组
        if hasattr(self, 'current_image_array') and self.current_image_array is not None:
            self.current_image_array = None
        
        # 清理图像查看器中的图像
        if hasattr(self, 'image_viewer') and self.image_viewer:
            if self.image_viewer.original_pixmap:
                self.image_viewer.original_pixmap = None
            if self.image_viewer.image_item:
                self.image_viewer.scene.removeItem(self.image_viewer.image_item)
                self.image_viewer.image_item = None
    
    def add_aoi(self):
        """添加AOI区域"""
        if not self.current_image_path:
            QMessageBox.warning(self, '警告', '请先打开一张图像')
            return
        
        # 当需要操作AOI时，自动显示AOI管理面板
        if not self.aoi_panel_visible:
            self.show_aoi_panel()
        
        self.image_viewer.start_aoi_creation()
        #self.statusbar.showMessage('点击并拖拽创建AOI区域')
    
    def on_aoi_created(self, aoi_data):
        """AOI创建完成的处理"""
        self.aoi_list.append(aoi_data)
        
        # 确保AOI管理面板可见
        if not self.aoi_panel_visible:
            self.show_aoi_panel()
        
        self.update_aoi_tree()
        
        # 新创建的AOI默认展开并选中
        for i in range(self.tree_aoi_list.topLevelItemCount()):
            item = self.tree_aoi_list.topLevelItem(i)
            if item.text(0) == aoi_data['name']:
                item.setExpanded(True)
                self.tree_aoi_list.setCurrentItem(item)
                
                # 设置为当前AOI并更新属性面板
                self.current_aoi = aoi_data
                self.updating_properties = True
                self.load_aoi_properties(aoi_data)
                self.updating_properties = False
                self.groupBox_aoi_properties.setEnabled(True)
                break
        
        #self.statusbar.showMessage(f'已创建AOI: {aoi_data["name"]}')
    
    def on_aoi_data_changed(self, aoi_data):
        """处理AOI数据变化（拖动、调整大小时实时更新）"""
        # 更新树形列表
        self.update_aoi_tree()
        
        # 如果当前选中的AOI正好是发生变化的AOI，则更新属性面板
        if (self.current_aoi and
                self.current_aoi.get('id') == aoi_data.get('id')):
            # 设置标志防止循环更新
            self.updating_properties = True
            self.load_aoi_properties(aoi_data)
            self.updating_properties = False
    
    def update_aoi_tree(self):
        """更新AOI树形列表"""
        # 保存当前展开状态
        expanded_items = set()
        for i in range(self.tree_aoi_list.topLevelItemCount()):
            item = self.tree_aoi_list.topLevelItem(i)
            if item.isExpanded():
                expanded_items.add(item.text(0))  # 使用AOI名称作为标识
        
        # 保存当前选中项
        current_selected = None
        current_item = self.tree_aoi_list.currentItem()
        if current_item:
            # 获取顶层项
            while current_item.parent():
                current_item = current_item.parent()
            current_selected = current_item.text(0)
        
        self.tree_aoi_list.clear()
        
        for aoi in self.aoi_list:
            aoi_item = QTreeWidgetItem([aoi['name'], ''])
            
            # 添加属性子项
            name_item = QTreeWidgetItem(['名称', aoi['name']])
            position_item = QTreeWidgetItem(['位置', f"({aoi['x']}, {aoi['y']})"])
            size_item = QTreeWidgetItem(['大小', f"{aoi['width']} x {aoi['height']}"])
            color_item = QTreeWidgetItem(['颜色', aoi['color']])
            desc_item = QTreeWidgetItem(['描述', aoi['description']])
            
            aoi_item.addChildren([name_item, position_item, size_item, 
                                 color_item, desc_item])
            
            # 存储AOI数据引用
            aoi_item.setData(0, Qt.UserRole, aoi)
            
            self.tree_aoi_list.addTopLevelItem(aoi_item)
            
            # 恢复展开状态
            if aoi['name'] in expanded_items:
                aoi_item.setExpanded(True)
            
            # 恢复选中状态
            if current_selected and aoi['name'] == current_selected:
                self.tree_aoi_list.setCurrentItem(aoi_item)
    
    def on_aoi_selected_from_image(self, aoi_data):
        """处理从图像中选中AOI的事件"""
        # 在树形列表中选中对应的AOI
        for i in range(self.tree_aoi_list.topLevelItemCount()):
            item = self.tree_aoi_list.topLevelItem(i)
            item_aoi_data = item.data(0, Qt.UserRole)
            if item_aoi_data and item_aoi_data.get('id') == aoi_data.get('id'):
                # 设置选中但不触发itemClicked信号，避免循环
                self.tree_aoi_list.blockSignals(True)
                self.tree_aoi_list.setCurrentItem(item)
                self.tree_aoi_list.blockSignals(False)
                
                # 更新当前AOI和属性面板
                self.current_aoi = aoi_data
                self.updating_properties = True
                self.load_aoi_properties(aoi_data)
                self.updating_properties = False
                self.groupBox_aoi_properties.setEnabled(True)
                break

    def on_aoi_selected(self, item):
        """AOI选择事件处理（从树形列表）"""
        # 获取顶层项（AOI项）
        while item.parent():
            item = item.parent()
        
        aoi_data = item.data(0, Qt.UserRole)
        if aoi_data:
            self.current_aoi = aoi_data
            self.updating_properties = True
            self.load_aoi_properties(aoi_data)
            self.updating_properties = False
            self.groupBox_aoi_properties.setEnabled(True)
            
            # 在图像中选中对应的AOI项
            for aoi_item in self.image_viewer.aoi_items:
                if aoi_item.aoi_data.get('id') == aoi_data.get('id'):
                    # 清除其他选中项
                    self.image_viewer.scene.clearSelection()
                    # 选中当前项
                    aoi_item.setSelected(True)
                    break
    
    def load_aoi_properties(self, aoi_data):
        """加载AOI属性到编辑区域"""
        self.lineEdit_name.setText(aoi_data.get('name', ''))
        self.spinBox_x.setValue(aoi_data.get('x', 0))
        self.spinBox_y.setValue(aoi_data.get('y', 0))
        self.spinBox_width.setValue(aoi_data.get('width', 100))
        self.spinBox_height.setValue(aoi_data.get('height', 100))
        self.textEdit_description.setPlainText(aoi_data.get('description', ''))
        
        # 设置颜色按钮
        color = aoi_data.get('color', 'red')
        self.btn_color.setStyleSheet(f'background-color: {color};')
    
    def on_property_changed(self):
        """属性变化处理"""
        # 如果正在更新属性，忽略信号避免循环
        if self.updating_properties:
            return
            
        # 对于名称变化，不自动应用，等待用户点击应用按钮
        # 其他属性变化则延迟自动应用
        sender = self.sender()
        if sender == self.lineEdit_name:
            # 名称变化时不自动应用，只有点击应用按钮才更新
            return
            
        if hasattr(self, 'property_update_timer'):
            self.property_update_timer.stop()
        
        # 延迟更新以避免频繁更新
        from PyQt5.QtCore import QTimer
        self.property_update_timer = QTimer()
        self.property_update_timer.setSingleShot(True)
        self.property_update_timer.timeout.connect(self.apply_changes)
        self.property_update_timer.start(500)  # 500ms延迟
    
    def apply_changes(self):
        """应用属性更改"""
        if not self.current_aoi:
            return
        
        # 设置标志防止循环更新
        self.updating_properties = True
        
        # 保存旧的名称，用于检查是否发生变化
        old_name = self.current_aoi.get('name', '')
        
        # 更新AOI数据
        self.current_aoi['name'] = self.lineEdit_name.text()
        self.current_aoi['x'] = self.spinBox_x.value()
        self.current_aoi['y'] = self.spinBox_y.value()
        self.current_aoi['width'] = self.spinBox_width.value()
        self.current_aoi['height'] = self.spinBox_height.value()
        description = self.textEdit_description.toPlainText()
        self.current_aoi['description'] = description
        
        # 找到对应的图形项并更新其属性
        for aoi_item in self.image_viewer.aoi_items:
            if aoi_item.aoi_data.get('id') == self.current_aoi.get('id'):
                # 更新图形项的位置和大小
                aoi_item.setPos(self.current_aoi['x'], self.current_aoi['y'])
                width = self.current_aoi['width']
                height = self.current_aoi['height']
                aoi_item.setRect(0, 0, width, height)
                
                # 更新颜色
                color = QColor(self.current_aoi.get('color', 'red'))
                pen_width = max(1.0, 2.0 / aoi_item.scale_factor)
                aoi_item.setPen(QPen(color, pen_width))
                aoi_item.setBrush(QBrush(color, Qt.NoBrush))
                
                # 更新tooltip为AOI名称
                aoi_item.setToolTip(self.current_aoi.get('name', ''))
                
                # 更新句柄位置
                aoi_item.update_handles()
                break
        
        # 更新显示
        self.update_aoi_tree()
        
        # 如果名称发生了变化，重新选中对应的项
        if old_name != self.current_aoi.get('name', ''):
            self.select_aoi_by_id(self.current_aoi.get('id'))
        
        #self.statusbar.showMessage('AOI属性已更新')
        
        # 清除标志
        self.updating_properties = False
    
    def select_aoi_by_id(self, aoi_id):
        """根据AOI ID选中树形列表中对应的项"""
        for i in range(self.tree_aoi_list.topLevelItemCount()):
            item = self.tree_aoi_list.topLevelItem(i)
            aoi_data = item.data(0, Qt.UserRole)
            if aoi_data and aoi_data.get('id') == aoi_id:
                self.tree_aoi_list.setCurrentItem(item)
                break
    
    def choose_color(self):
        """选择颜色"""
        if not self.current_aoi:
            return
        
        current_color = QColor(self.current_aoi.get('color', 'red'))
        color = QColorDialog.getColor(current_color, self, '选择AOI颜色')
        
        if color.isValid():
            color_name = color.name()
            self.current_aoi['color'] = color_name
            self.btn_color.setStyleSheet(f'background-color: {color_name};')
            
            # 找到对应的图形项并更新颜色
            for aoi_item in self.image_viewer.aoi_items:
                if aoi_item.aoi_data.get('id') == self.current_aoi.get('id'):
                    aoi_item.update_color(color_name)
                    break
            
            # 更新树形列表显示
            self.update_aoi_tree()
            #self.statusbar.showMessage('AOI颜色已更新')
    
    def delete_aoi(self):
        """删除选中的AOI"""
        # 确保AOI管理面板可见以便用户确认删除操作
        if not self.aoi_panel_visible:
            self.show_aoi_panel()
            
        current_item = self.tree_aoi_list.currentItem()
        if not current_item:
            QMessageBox.warning(self, '警告', '请先选择一个AOI')
            return
        
        # 获取顶层项
        while current_item.parent():
            current_item = current_item.parent()
        
        aoi_data = current_item.data(0, Qt.UserRole)
        if aoi_data:
            reply = QMessageBox.question(
                self, '确认删除', 
                f'确定要删除AOI "{aoi_data["name"]}" 吗？',
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No)
            
            if reply == QMessageBox.Yes:
                # 从列表中移除
                self.aoi_list.remove(aoi_data)
                
                # 从图像查看器中移除
                for item in self.image_viewer.aoi_items[:]:
                    if item.aoi_data == aoi_data:
                        self.image_viewer.scene.removeItem(item)
                        self.image_viewer.aoi_items.remove(item)
                        break
                
                # 更新界面
                self.update_aoi_tree()
                self.groupBox_aoi_properties.setEnabled(False)
                self.current_aoi = None
                
                #self.statusbar.showMessage(f'已删除AOI: {aoi_data["name"]}')
    
    def clear_all_aoi(self):
        """清空所有AOI"""
        if not self.aoi_list:
            return
        
        reply = QMessageBox.question(
            self, '确认清空', '确定要清空所有AOI吗？',
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            self.aoi_list.clear()
            
            # 清空图像查看器中的AOI
            for item in self.image_viewer.aoi_items[:]:
                self.image_viewer.scene.removeItem(item)
            self.image_viewer.aoi_items.clear()
            
            # 更新界面
            self.update_aoi_tree()
            self.groupBox_aoi_properties.setEnabled(False)
            self.current_aoi = None
            self.image_viewer.aoi_counter = 1
            
            #self.statusbar.showMessage('已清空所有AOI')
    
    def save_aoi_data(self):
        """保存AOI数据到文件"""
        if not self.aoi_list:
            QMessageBox.warning(self, '警告', '没有AOI数据可保存')
            return
        
        file_path, _ = QFileDialog.getSaveFileName(
            self, '保存AOI项目',
            '', 'JSON Files (*.json)')
        
        if file_path:
            try:
                # 构建项目数据
                project_data = {
                    'project_info': {
                        'name': os.path.splitext(os.path.basename(file_path))[0],
                        'created_time': __import__('datetime').datetime.now()
                                       .strftime('%Y-%m-%d %H:%M:%S'),
                        'version': '1.0'
                    },
                    'image_path': self.current_image_path,
                    'aoi_count': len(self.aoi_list),
                    'aoi_list': self.aoi_list
                }
                
                with open(file_path, 'w', encoding='utf-8') as f:
                    json.dump(project_data, f, ensure_ascii=False, indent=2)
                
                #self.statusbar.showMessage(
                    #f'项目已保存到: {os.path.basename(file_path)}')
                
            except Exception as e:
                QMessageBox.critical(self, '错误', f'保存失败:\n{str(e)}')
    
    def load_aoi_data(self):
        """从文件加载AOI数据（仅加载AOI，不加载图像）"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, '加载AOI数据',
            '', 'JSON Files (*.json)')
        
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    project_data = json.load(f)
                
                # 只加载AOI数据，跳过图像加载
                self.aoi_list = project_data.get('aoi_list', [])
                
                # 更新AOI计数器，确保新创建的AOI ID不冲突
                if self.aoi_list:
                    max_id = max(aoi.get('id', 0) for aoi in self.aoi_list)
                    self.image_viewer.aoi_counter = max_id + 1
                
                # 更新AOI树形列表
                self.update_aoi_tree()
                
                # 如果当前已经有图像加载，则重新创建AOI图形项
                if self.current_image_path:
                    self.recreate_aoi_items()
                    # 适应窗口大小
                    self.fit_to_window()
                
                # 显示加载结果
                aoi_count = len(self.aoi_list)
                QMessageBox.information(self, '加载成功', 
                                    f'已加载 {aoi_count} 个AOI区域\n'
                                    f'文件: {os.path.basename(file_path)}')
                
            except Exception as e:
                QMessageBox.critical(self, '错误', f'加载AOI数据失败:\n{str(e)}')
    
    def recreate_aoi_items(self):
        """重新创建AOI图形项"""
        # 清空现有的AOI项
        for item in self.image_viewer.aoi_items[:]:
            self.image_viewer.scene.removeItem(item)
        self.image_viewer.aoi_items.clear()
        
        # 重新创建AOI项
        for aoi_data in self.aoi_list:
            rect = QRectF(0, 0, aoi_data['width'], aoi_data['height'])
            aoi_item = ResizableRectItem(
                rect, aoi_data, self.image_viewer.scale_factor,
                self.image_viewer)
            aoi_item.setPos(aoi_data['x'], aoi_data['y'])
            
            # 设置tooltip为AOI名称
            aoi_item.setToolTip(aoi_data.get('name', ''))
            
            self.image_viewer.scene.addItem(aoi_item)
            self.image_viewer.aoi_items.append(aoi_item)
    
    def fit_to_window(self):
        """适应窗口大小"""
        self.image_viewer.zoom_to_fit()
    
    def actual_size(self):
        """实际大小显示"""
        self.image_viewer.zoom_to_actual()
    
    def on_mouse_moved(self, x, y):
        """鼠标移动处理"""
        position_text = f'鼠标位置: ({x}, {y})'
        
        # 获取当前位置的像素值
        if (hasattr(self, 'current_image_array') and
                self.current_image_array is not None):
            
            try:
                # 确保坐标在图像范围内
                h, w = self.current_image_array.shape[:2]
                if 0 <= x < w and 0 <= y < h:
                    if len(self.current_image_array.shape) == 3:
                        # 彩色图像，显示RGB值 (OpenCV格式是BGR)
                        b, g, r = self.current_image_array[y, x]
                        pixel_text = f' | RGB: ({r}, {g}, {b})'
                    else:
                        # 灰度图像
                        gray_value = self.current_image_array[y, x]
                        pixel_text = f' | 灰度: {gray_value}'
                    
                    position_text += pixel_text
            except (IndexError, ValueError):
                pass  # 坐标超出范围或其他错误，只显示位置
        
        self.label_mouse_pos.setText(position_text)
    
    def on_scale_changed(self, scale_percent):
        """缩放变化处理"""
        if hasattr(self, 'label_zoom_value'):
            self.label_zoom_value.setText(f'{scale_percent}%')
    
    def show_aoi_context_menu(self, position):
        """显示AOI右键菜单"""
        item = self.tree_aoi_list.itemAt(position)
        if item is None:
            return
        
        # 获取顶层项（AOI项）
        while item.parent():
            item = item.parent()
        
        aoi_data = item.data(0, Qt.UserRole)
        if not aoi_data:
            return
        
        # 创建右键菜单
        from PyQt5.QtWidgets import QMenu
        context_menu = QMenu(self)
        
        # 添加"保存AOI区域图片"动作
        save_aoi_image_action = context_menu.addAction("保存AOI区域图片")
        save_aoi_image_action.triggered.connect(lambda: self.save_aoi_image(aoi_data))
        
        # 显示菜单
        context_menu.exec_(self.tree_aoi_list.mapToGlobal(position))
    
    def save_aoi_image(self, aoi_data):
        """保存AOI区域的图片"""
        if self.current_image_array is None:
            QMessageBox.warning(self, '警告', '没有图像可保存')
            return
        
        # 获取AOI区域坐标
        x = aoi_data['x']
        y = aoi_data['y']
        width = aoi_data['width']
        height = aoi_data['height']
        aoi_name = aoi_data['name']
        
        # 检查坐标是否有效
        img_height, img_width = self.current_image_array.shape[:2]
        if (x < 0 or y < 0 or x + width > img_width or y + height > img_height):
            QMessageBox.warning(self, '警告', 
                              f'AOI区域超出图像边界\n'
                              f'AOI: ({x}, {y}, {width}, {height})\n'
                              f'图像尺寸: {img_width} x {img_height}')
            return
        
        # 裁剪AOI区域
        try:
            aoi_image = self.current_image_array[y:y+height, x:x+width]
            
            if aoi_image.size == 0:
                QMessageBox.warning(self, '警告', 'AOI区域为空')
                return
            
            # 获取原始图像的文件扩展名
            if self.current_image_path:
                original_ext = os.path.splitext(self.current_image_path)[1].lower()
                if not original_ext:
                    original_ext = '.jpg'
            else:
                original_ext = '.jpg'
            
            # 设置默认保存文件名
            default_filename = f"{aoi_name}_AOI{original_ext}"
            
            # 弹出保存对话框
            file_path, selected_filter = QFileDialog.getSaveFileName(
                self, f'保存AOI区域图片 - {aoi_name}',
                default_filename,
                'Image Files (*.png *.jpg *.jpeg *.bmp *.tiff *.tif);;'
                'PNG Files (*.png);;'
                'JPEG Files (*.jpg *.jpeg);;'
                'BMP Files (*.bmp);;'
                'TIFF Files (*.tiff *.tif)')
            
            if file_path:
                # 保存AOI区域图片
                success = save_image_chinese_path(aoi_image, file_path)
                
                if success:
                    QMessageBox.information(self, '成功', 
                                          f'AOI区域图片已保存到:\n{file_path}')
                else:
                    QMessageBox.critical(self, '错误', '保存AOI区域图片失败')
        
        except Exception as e:
            QMessageBox.critical(self, '错误', f'保存AOI区域图片失败:\n{str(e)}')
    
    def show_about(self):
        """显示关于对话框"""
        QMessageBox.about(
            self, '关于',
            '图像AOI编辑器 v1.0\n\n'
            '功能特点:\n'
            '• 支持多种图像格式\n'
            '• 可缩放查看图像\n'
            '• 创建和编辑AOI区域\n'
            '• 拖拽和调整AOI大小\n'
            '• 保存和加载项目文件\n\n'
            '使用OpenCV和PyQt5开发')
    def get_aoi_list_from_tree(self) -> List[Dict]:
        """
        从AOI树中提取所有AOI数据，返回List[Dict]格式
        
        Returns:
            List[Dict]: AOI数据列表
        """
        aoi_list = []
        
        # 遍历所有顶层项
        for i in range(self.tree_aoi_list.topLevelItemCount()):
            top_item = self.tree_aoi_list.topLevelItem(i)
            aoi_data = top_item.data(0, Qt.UserRole)
            
            if aoi_data:
                # 直接添加AOI数据的副本
                aoi_list.append(dict(aoi_data))
        
        return aoi_list

    def normalize_image_for_display(self, image):
        """
        将图像标准化为适合显示的格式
        
        Args:
            image: 输入图像，任意数据类型
            
        Returns:
            numpy.ndarray: uint8格式的图像，像素值范围0-255
        """
        if image is None:
            return None
        
        # 获取图像信息
        dtype = image.dtype

        
        # uint8 图像直接返回
        if dtype == np.uint8:
            return image.copy()
        
        # 处理单通道图像
        if len(image.shape) == 2:
            # 单通道图像：直接计算99%分位数
            p1 = np.percentile(image, 0.05)
            p99 = np.percentile(image, 99.95)
            

            
            if p99 > p1:
                clipped_image = np.clip(image.astype(np.float64), p1, p99)
                normalized = ((clipped_image - p1) / (p99 - p1) * 255.0).astype(np.uint8)

            else:
                normalized = np.zeros_like(image, dtype=np.uint8)

        
        # 处理多通道图像
        elif len(image.shape) == 3:
            # 多通道图像：分别计算每个通道的99%分位数
            channels = []
            height, width, num_channels = image.shape
            
  
            
            for c in range(num_channels):
                channel = image[:, :, c]
                
                # 计算当前通道的1%和99%分位数
                p1 = np.percentile(channel, 0.05)
                p99 = np.percentile(channel, 99.95)
                
                
                
                if p99 > p1:
                    # 对当前通道进行映射
                    clipped_channel = np.clip(channel.astype(np.float64), p1, p99)
                    normalized_channel = ((clipped_channel - p1) / (p99 - p1) * 255.0).astype(np.uint8)
                else:
                    # 常数通道
                    normalized_channel = np.zeros_like(channel, dtype=np.uint8)
                  
                
                channels.append(normalized_channel)
            
            # 合并所有通道
            normalized = np.stack(channels, axis=2)
           
        
        else:
            # 其他维度的图像
          
            # 展平处理
            p1 = np.percentile(image, 1)
            p99 = np.percentile(image, 99)
            
            if p99 > p1:
                clipped_image = np.clip(image.astype(np.float64), p1, p99)
                normalized = ((clipped_image - p1) / (p99 - p1) * 255.0).astype(np.uint8)
            else:
                normalized = np.zeros_like(image, dtype=np.uint8)
        

        return normalized

def main():
    """主函数"""
    app = QApplication(sys.argv)
    
    # 检查UI文件是否存在
    ui_path = os.path.join(os.path.dirname(__file__), 'image_aoi_editor.ui')
    if not os.path.exists(ui_path):
        QMessageBox.critical(None, '错误', 
                           f'找不到UI文件: {ui_path}\n'
                           '请确保image_aoi_editor.ui文件在同一目录下')
        return
    
    try:
        window = ImageAOIEditor()
        window.show()
        
        print("图像AOI编辑器启动成功！")
        print("功能说明:")
        print("1. 点击'打开图片'加载图像")
        print("2. 点击'添加AOI'进入创建模式")
        print("3. 在图像上拖拽创建AOI区域")
        print("4. 在右侧树形列表中管理AOI")
        print("5. 可以拖拽、缩放AOI区域")
        print("6. 按F2键切换AOI管理面板的显示/隐藏")
        print("7. AOI操作时会自动显示管理面板")
        
        sys.exit(app.exec_())
        
    except Exception as e:
        QMessageBox.critical(None, '启动错误', f'程序启动失败:\n{str(e)}')


if __name__ == '__main__':
    main()
