import math
import os
import re

import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog
from read_data import *
from PIL import Image
# from X1 import *
import shutil
import sys
import pickle
import subprocess
import textwrap

import numpy as np
import pandas as pd
import tkinter as tk
from tkinter import ttk
# from ttkbootstrap import Style
from tkinter import messagebox
import tkinter.scrolledtext as st
from PIL import Image, ImageTk
from matplotlib import pyplot as plt
from tkinter import Canvas, Frame, BOTH, PhotoImage, filedialog

class Interactive_CNN:
    def __init__(self):
        self.selection_rect = None  # 被选中的方框的标签
        self.selection_index = None  # 被选中的图元或线所在总表的层数
        self.selection_type = None  # 被选中的类型 1：图元 2：线
        self.Dragged_item = ()  # 记录被拖拽的图元id
        self.Yt = 150  # 新图元的y坐标
        self.Original_x = 0  # 记录图元原来的x坐标
        self.Original_y = 0  # 记录图元原来的y坐标
        self.Offset_x = 0  # 记录鼠标相对于图元的x偏移量
        self.Offset_y = 0  # 记录鼠标相对于图元的y偏移量
        self.Root = tk.Tk()
        # 设置窗口的大小为1200*750
        window_width = 1400  # 窗口的宽度
        window_height = 900  # 窗口的高度
        self.Root.title("神经网络可视化")
        self.Root.geometry("1400x900")  # 设置窗口的大小和位置
        img_path = ["img/data.png", "img/conv.png", "img/pool.png", "img/full_connect.png", "img/nonlinear.png",
                    "img/classifier.png", "img/error.png", "img/adjust.png", "img/adjust.png", "img/adjust.png", "img/arrow1.png", "img/arrow2.png"]
        self.List_image = []
        for path in img_path[:-2]:
            self.List_image.append(self.element(path, 70, 60))
        self.List_image.append(self.element(img_path[-2], 70, 30))
        self.List_image.append(self.element(img_path[-1], 70, 30))
        # 创建一个画布，用于绘制矩形框，设置画布的大小和背景色
        self.Viewcanvas = tk.Canvas(self.Root, width=window_width, height=window_height, bg="white")
        self.Viewcanvas.pack()  # 将画布添加到主窗口中
        # 绘制矩形框，使用不同的颜色和线宽，指定矩形框的左上角和右下角坐标，填充色，边框色和边框宽度
        self.Viewcanvas.create_rectangle(20, 20, 370, 580, fill=None, outline="lightblue", width=2)
        self.Viewcanvas.create_rectangle(380, 40, 1390, 580, fill=None, outline="lightblue", width=2)
        self.Viewcanvas.create_rectangle(20, 620, 1390, 880, fill=None, outline="lightblue", width=2)
        self.Text = st.ScrolledText(self.Root)  # 创建一个带滚动条的文本框，用于展示计算过程
        self.Text.place(x=20, y=620, width=1365, height=260)
        self.button = {}
        # 按钮顺序：加载已有模型、新建模型、图元参数设置、保存模型、模型正确性检查、退出、训练参数设置、训练、输出模型程序
        names = ['加载已有模型', '新建模型', '图元参数设置', '删除图元或线','保存模型', '模型正确性检查', '退出', '训练参数设置', '训练', '输出模型程序']
        positions = [(385, 10), (550, 10), (675, 10), (840, 10), (1005, 10), (1130, 10), (1325, 10), (20, 590), (200, 590),
                     (300, 590)]
        bg = ['#32CD32', '#32CD32', '#32CD32', '#32CD32', '#32CD32', '#32CD32', '#FF0000', '#436EEE', '#436EEE', '#436EEE']
        for i in range(9):
            button = tk.Button(self.Root, text=' '+names[i]+' ', bg=bg[i], bd=0, relief=tk.FLAT,
                               highlightthickness=0, font=("黑体", 14))
            button.place(x=positions[i][0], y=positions[i][1])
            self.button['button' + str(i+1)] = button
    def button_link(self):
        # 替每个主界面的按钮绑定各自的函数
        self.button['button1'].config(command=Mod.load_model)  # 加载已有模型
        self.button['button2'].config(command=self.restart_new_model)  # 新建模型
        self.button['button3'].config(command=Net.setpara_func)  # 图元参数设置
        self.button['button4'].config(command=Net.delete_model_conn)  # 删除图元或线
        self.button['button5'].config(command=Mod.save_model)  # 保存模型
        self.button['button6'].config(command=Net.check_connection)  # 模型正确性检查
        self.button['button7'].config(command=self.close_program)  # 退出
        self.button['button8'].config(command=Mod.train_setting)  # 训练参数
        self.button['button9'].config(command=Mod.train)  # 训练
    def close_program(self):
        sys.exit()


    def element(self, path, width, height):
        # 加载图元对应的图片文件
        img = Image.open(path)
        # 使用resize方法调整图片
        img = img.resize((width, height))
        # 把Image对象转换成PhotoImage对象
        img = ImageTk.PhotoImage(img)
        # 保存图片的引用，防止被垃圾回收
        self.Root.img = img
        return img

    def redirector(self, inputStr):
        self.Text.configure(state="normal")  # 设置文本框为可编辑状态
        self.Text.insert("end", inputStr)  # 在文本框末尾插入内容
        self.Text.see("end")  # 滚动到末尾
        self.Text.configure(state="disabled")  # 设置文本框为只读状态

    def create_left_element(self, List_image):
        # 图元列表，在窗口左侧创建图元菜单、
        element_list = [(135, 60), (320, 60), (135, 156), (320, 156), (135, 252), (320, 252),
                        (135, 348), (320, 348), (135, 444), (320, 444), (135, 540), (320, 540)]
        for i in range(12):
            # 获取图元对象的类型、标签等信息
            obj_type = i + 1
            # 并且要根据需求调整每个对象的位置
            obj_x = element_list[i][0]  # 根据对象的id计算x坐标
            obj_y = element_list[i][1]  # 根据对象的id计算y坐标
            # 根据对象的类型，绘制相应的图形
            if obj_type == 1:  # 加载数据集
                l1 = self.element_binding(obj_x, obj_y, List_image[0], 3, " 加载" + "\n" + "数据集")
            elif obj_type == 2:  # 卷积
                l1 = self.element_binding(obj_x, obj_y, List_image[1], 0, "卷积")
            elif obj_type == 3:  # 池化
                l1 = self.element_binding(obj_x, obj_y, List_image[2], 0, "池化")
            elif obj_type == 4:  # 全连接
                l1 = self.element_binding(obj_x, obj_y, List_image[3], 0, "全连接" + "\n" + " 函数")
            elif obj_type == 5:  # 非线性
                l1 = self.element_binding(obj_x, obj_y, List_image[4], 0, "非线性" + "\n" + " 函数")
            elif obj_type == 6:  # 分类
                l1 = self.element_binding(obj_x, obj_y, List_image[5], 0, "类别")
            elif obj_type == 7:  # 误差计算
                l1 = self.element_binding(obj_x, obj_y, List_image[6], 0, "误差")
            elif obj_type == 8:  # 调整
                l1 = self.element_binding(obj_x, obj_y, List_image[7], 0, "调整1")
            elif obj_type == 9:  # 调整
                l1 = self.element_binding(obj_x, obj_y, List_image[8], 0, "调整2")
            elif obj_type == 10:  # 调整
                l1 = self.element_binding(obj_x, obj_y, List_image[9], 0, "调整3")
            elif obj_type == 11:  # 调整
                l1 = self.element_binding(obj_x, obj_y, List_image[10], 3, "主数据" + "\n" + "连接线")
            elif obj_type == 12:  # 调整
                l1 = self.element_binding(obj_x, obj_y, List_image[11], 3, " 参数" + "\n" + "连接线")
            # 为左边菜单的每个图元绑定鼠标按下、移动和松开的事件
            self.Viewcanvas.tag_bind(l1, "<Button-1>", self.on_drag_start)
            self.Viewcanvas.tag_bind(l1, "<B1-Motion>", self.on_drag_motion)
            self.Viewcanvas.tag_bind(l1, "<ButtonRelease-1>", self.on_drag_end)
    
    #############################################################################################################################
    def new_element_bind(self, newitem):
        self.Viewcanvas.tag_bind(newitem, "<Button-1>", self.on_drag_start)
        self.Viewcanvas.tag_bind(newitem, "<B1-Motion>", self.on_drag_motion)
        self.Viewcanvas.tag_bind(newitem, "<ButtonRelease-1>", self.new_on_drag_end)  
        
    def conn_bind(self, connid):
        self.Viewcanvas.tag_bind(connid, "<Button-1>", self.on_click_conn)
    
    def on_click_conn(self, event):
        # 获取当前图元的id
        # 这个物体的id是由tkinter自动分配的。当在画布上创建一个物体时，它会返回一个唯一的整数id，可以用它来引用这个物体
        self.Dragged_item = event.widget.find_closest(event.x, event.y)
        obj_id1 = self.Dragged_item[0]  # 获取被拖拽的图元的id
        # x1, y1, x2, y2 = self.Viewcanvas.coords(obj_id1)
        # # 计算线条的宽度和高度
        # width = abs(x2 - x1)
        # height = abs(y2 - y1)
        # # 计算线条的中心点
        # x_center = (x1 + x2) / 2
        # y_center = (y1 + y2) / 2
        # # 创建一个比线条稍宽的矩形作为轮廓
        # outline_id = self.Viewcanvas.create_rectangle(
        #     x_center - width / 2 - 2, y_center - height / 2 - 2,
        #     x_center + width / 2 + 2, y_center + height / 2 + 2,
        #     outline='black', width=2
        # )
        self.Viewcanvas.delete(self.selection_rect) # 把框删掉
        for index, sublist in enumerate(Net.linesIDs):
        # 检查子列表中是否包含obj_id1
            if obj_id1 == sublist:
                self.selection_index = index  # 找出被左键点击的连线在网络连线总表的位置
                self.selection_type = 2
        
        
    def new_on_drag_end(self, event):
        obj_id1 = self.Dragged_item[0]  # 获取被拖拽的图元的id
        #  使用 print(obj_id1) 从而查看各个图元的id,此步省略
        # self.on_drag(obj_id1, event)
        for index, sublist in enumerate(Net.Items1):
        # 检查子列表中是否包含obj_id1
            if obj_id1 == sublist[0]:
                x = event.x + CNN.Offset_x
                y = event.y + CNN.Offset_y
                Net.Items1[index] = [obj_id1, x, y]  # 更新被移动的图元的新坐标
                
                motion_element_name = Net.AllModelObj[index].ObjID  # 找出被移动的图元名称
                for row, conn in enumerate(Net.AllModelConn):
                    if motion_element_name in conn:
                        site = conn.index(motion_element_name)  # 确定移动的图元是箭头的开始还是结束
                        linesID = Net.AllModelConn[row][0]
                        current_coor = self.Viewcanvas.coords(linesID)
                        x1, y1, x2, y2 = current_coor  # 得到现在的箭头起点终点坐标
                        (Net.new_conn_lines(linesID, (x, y), (x2, y2), 1)) if site == 2 else (Net.new_conn_lines(linesID, (x1, y1), (x, y), 2))
                break


        
    #############################################################################################################################    
    def element_binding(self, obj_x, obj_y, image, y, text):
        l1 = self.Viewcanvas.create_image(obj_x, obj_y, image=image)  # 创建图元对象
        # 创建图元对象的标签
        self.Viewcanvas.create_text(obj_x - 80, obj_y + y, text=text, font=("黑体", 14))
        return l1

    # 定义图元拖拽开始的函数
    def on_drag_start(self, event):
        # 获取当前图元的id
        # 这个物体的id是由tkinter自动分配的。当在画布上创建一个物体时，它会返回一个唯一的整数id，可以用它来引用这个物体
        self.Dragged_item = event.widget.find_closest(event.x, event.y)
        self.Original_x = event.widget.coords(self.Dragged_item)[0]  # 获取当前图元的x坐标
        self.Original_y = event.widget.coords(self.Dragged_item)[1]  # 获取当前图元的y坐标
        self.Offset_x = self.Original_x - event.x  # 计算鼠标相对于图元的x偏移量
        self.Offset_y = self.Original_y - event.y  # 计算鼠标相对于图元的y偏移量
        #######################################################################################
        obj_id1 = self.Dragged_item[0]  # 获取被拖拽的图元的id
        #  使用 print(obj_id1) 从而查看各个图元的id,此步省略
        for index, sublist in enumerate(Net.Items1):
        # 检查子列表中是否包含obj_id1
            if obj_id1 == sublist[0]:
                x = event.x + CNN.Offset_x
                y = event.y + CNN.Offset_y
                self.selection_index = index  # 找出被左键点击的图元在网络模型总表的位置
                self.selection_type = 1
        # 给点击的图元套框
        if obj_id1 != 24 and obj_id1 != 26:
            self.Viewcanvas.delete(self.selection_rect)
            self.selection_rect = self.Viewcanvas.create_rectangle(self.Original_x-40, self.Original_y-35, self.Original_x+40, self.Original_y+35, outline="orange", width=2)
        #######################################################################################

    # 定义图元拖拽移动的函数
    def on_drag_motion(self, event):
        x = event.x + self.Offset_x  # 计算图元新的x坐标
        y = event.y + self.Offset_y  # 计算图元新的y坐标
        event.widget.coords(self.Dragged_item, x, y)  # 更新图元的位置
        
        index = None
        for i, item in enumerate(Net.Second_Items[0]):
            if item[0] == self.Dragged_item[0]:
                index = i
                break
        #######################################################################################
        if self.Dragged_item[0] > 26:  # 更新右侧图元的标签的位置
                Dragged_label_item = (self.Dragged_item[0] + 1,)  ###########
                event.widget.coords(Dragged_label_item, x, y + 45)  ###########
        # 给拖动的图元套框
        obj_id1 = self.Dragged_item[0]  # 获取被拖拽的图元的id
        if obj_id1 != 24 and obj_id1 != 26:
            self.Viewcanvas.delete(self.selection_rect)
            self.selection_rect = self.Viewcanvas.create_rectangle(x-40, y-35, x+40, y+35, outline="orange", width=2)
        #######################################################################################
                
    # 定义图元拖拽结束的函数
    def on_drag_end(self, event):
        obj_id1 = self.Dragged_item[0]  # 获取被拖拽的图元的id
        #  使用 print(obj_id1) 从而查看各个图元的id,此步省略
        # 根据图元的类型，复制相应的图片到右侧画布
        self.on_drag(obj_id1, event)

    def on_drag(self, obj_id1, event): # 左侧函数绑定
        if obj_id1 == 4:  # 数据集
            # 调用 entity_loading_dataset 方法
            Net.entity_loading_dataset(event)
        elif obj_id1 == 6: # 卷积
            # 调用 convolution 方法
            Net.convolution(event)
        elif obj_id1 == 8: # 池化
            # 调用 pooling 方法
            Net.pooling(event)
        elif obj_id1 == 10: # 全连接
            # 调用 fully_connected 方法
            Net.fully_connected(event)
        elif obj_id1 == 12: # 非线性
            # 调用 nonlinear 方法
            Net.nonlinear(event)
        elif obj_id1 == 14: # 分类
            # 调用 class_ification 方法
            Net.class_ification(event)
        elif obj_id1 == 16: # 误差
            # 调用 error_calculation 方法
            Net.error_calculation(event)
        elif obj_id1 == 18: # 调整1
            # 调用 adjust_one 方法
            Net.adjust_one(event)
        elif obj_id1 == 20: # 调整2
            # 调用 adjust_two 方法
            Net.adjust_two(event)
        elif obj_id1 == 22: # 调整3
            # 调用 adjust_three 方法
            Net.adjust_three(event)
        elif obj_id1 == 24: # 主数据线
            # 调用 main_line 方法，参数为 1
            Net.main_line(event, 1)
        elif obj_id1 == 26: # 参数数据线
            # 调用 main_line 方法，参数为 0
            Net.main_line(event, 0)

    # 创建弹出窗口
    def creat_window(self, title):
        top = tk.Toplevel(self.Root)
        top.geometry("300x350")
        top.title(title)
        return top

    # 输入框
    def create_input_box(self, top, text, value):
        # 创建一个标签，显示指定的文本
        box_label = tk.Label(top, text=text)
        box_label.pack(padx=10, pady=10)  # 设置标签的外边距
        # 创建一个 IntVar 对象，并设置默认值为传入的 value 参数
        box_size = tk.IntVar(top, value=value)
        # 创建一个输入框，并关联到上面创建的 IntVar 对象
        box_size_entry = tk.Entry(top, textvariable=box_size)
        # 设置输入框的外边距
        box_size_entry.pack(padx=20, pady=20)
        # 返回创建的输入框，以便在调用该方法的地方使用
        return box_size_entry

    def create_dropdown_box(self, top, text, listvalues, default_value):
        # 创建一个标签，显示指定的文本
        box_mode_label = tk.Label(top, text=text)
        box_mode_label.pack(padx=10, pady=10)  # 设置标签的外边距
        # 创建一个下拉框（Combobox），用于选择
        box_mode_combobox = ttk.Combobox(top)
        # 设置下拉框的可选值
        box_mode_combobox["values"] = listvalues
        # 设置下拉框的默认值
        box_mode_combobox.set(default_value)
        # 设置下拉框的外边距
        box_mode_combobox.pack(padx=20, pady=15)
        # 返回创建的下拉框对象，以便在调用该方法的地方使用
        return box_mode_combobox

    # 清空当前模型展示区
    def restart_new_model(self):
        # 这将在子进程中调用当前Python解释器，并传递当前文件的路径作为参数
        subprocess.call([sys.executable, __file__])

class ModelConn:
    def __init__(self, ConnObjID, ConnType, NobjS, NobjE):
        self.ConnObjID = ConnObjID  # 连接线编号
        self.ConnType = ConnType  # 连接线类别
        self.NobjS = NobjS  # 源图元对象
        self.NobjE = NobjE  # 目标图元对象

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ConnObjID, self.ConnType, self.NobjS, self.NobjE]
        return result

class Networking:
    def __init__(self):
        self.AllModelObj = []  # 网络对象总表
        self.AllModelConn = []  # 网络连接对象总表
        self.RIGHT_MODEL = [
            [0, 1, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 1, 0, 0, 0, 0, 0, 0],
            [0, 1, 0, 1, 0, 0, 0, 0, 0],
            [0, 0, 0, 1, 1, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 1, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 1, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 1, 1],
            [0, 1, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 1, 0, 0, 0, 0, 0]
        ]
        self.Items1 = []  # 存储被拖拽的图元的列表
        self.Second_Items = [[], [], []]  # 记录第二个框中画布创建的序号(图元，标签，连线（没有用到连线）
        self.linesIDs = []  # 储存连线的ID
        self.SubFolders = []  # 存储数据集类别
        self.Element = []  # 记录图元名称
        self.Train_images = []  # 存储训练集的列表
        self.Test_images = []  # 存储测试集的列表
        self.ImgPath = []   # 储存数据集路径
        self.Item_Record = []
        self.Item_Name = []

    def entity_loading_dataset(self, event):
        CNN.Yt = event.y + CNN.Offset_y
        i, x, y, newitem = self.element_adjustment(event, '数据集', 0)
        name = '数据集' + str(i)
        # 复制图元对象的标签
        self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
        
        DataSet = Data_Class("DataSet" + str(i), 1, '数据集' + str(i), ".", x, y)
        self.AllModelObj.append(DataSet)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        # 创建一个tkinter根窗口，并隐藏它
        root1 = tk.Tk()
        root1.withdraw()
        # setload_data()函数，获取加载数据集的参数
        DataPara = DataSet.SetDataPara()
        self.ImgPath.append(DataPara['train_imgPath'])
        self.ImgPath.append(DataPara['test_imgPath'])
        # 调用X3中load_data()函数，根据参数加载数据集
        self.Train_images, self.Test_images = DataSet.LoadData(DataPara)
        # 打印一些信息，检查是否正确加载
        print("训练集图片的形状：", self.Train_images.shape)
        DataSet.images_statistics(DataPara['train_imgPath'])
        print("测试集图片的形状：", self.Test_images.shape)
        DataSet.images_statistics(DataPara['test_imgPath'])
        CNN.new_element_bind(newitem)

    def convolution(self, event):
        i, x, y, newitem = self.element_adjustment(event, '卷积', 1)

        Conv = Conv_Class("Conv" + str(i), 2, "卷积"+str(i), [], x, CNN.Yt)
        self.AllModelObj.append(Conv)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        top = CNN.creat_window('卷积参数配置')  # 创建弹出窗口
        kernel_size_entry = CNN.create_input_box(top, "卷积核大小：", 3)  # 创建一个输入框，获取卷积核大小
        stride_entry = CNN.create_input_box(top, "卷积步长：", 1)  # 创建一个输入框，获取卷积步长
        name = '卷积' + ' ' + str(i) + ':'
        def get_input():  # 创建
            # 获取输入框的内容
            result1 = int(kernel_size_entry.get())
            result2 = int(stride_entry.get())
            text = str(result1) + "*" + str(result1)
            self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name+text), '卷积' +  str(i)])  # 复制图元对象的标签
            # 参数赋值
            self.AllModelObj[-1].ParaString.append([result1, result2])
            self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetConvPara(self.AllModelObj[-1]))
            # print(self.AllModelObj[-1].ParaString)
            top.destroy()  # 关闭窗口
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
        CNN.new_element_bind(newitem)

    def pooling(self, event):
        i, x, y, newitem = self.element_adjustment(event, '池化', 2)

        Pool = Pool_Class("Pool" + str(i), 3, "池化" + str(i), [], x, CNN.Yt)
        self.AllModelObj.append(Pool)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        # 创建弹出窗口
        top = CNN.creat_window("池化参数配置")
        pool_type_combobox = CNN.create_dropdown_box(top, "池化池类型：", ["max", "avg", "min"], 'max')  # 创建一个下拉框，用于选择池化类型
        pool_size_entry = CNN.create_input_box(top, "池化池大小：", 2)  # 创建一个输入框，用于输入池大小
        name = '池化' + ' ' + str(i) + ':'
        # 创建
        def get_input():
            # 从下拉框中获取池化类型
            pool_type = pool_type_combobox.get()
            # 从输入框中获取池大小
            pool_size = int(pool_size_entry.get())
            text = str(pool_size) + "*" + str(pool_size) + str(pool_type)
            self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name+text), '池化' + str(i)])  # 复制图元对象的标签
            self.AllModelObj[-1].ParaString.append([pool_type, pool_size])
            self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetPollPara(self.AllModelObj[-1]))
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
        CNN.new_element_bind(newitem)


    def fully_connected(self, event):
        i, x, y, newitem = self.element_adjustment(event, '全连接', 3)

        FullConn = FullConn_Class("FullConn" + str(i), 4, "全连接" + str(i), [], x, CNN.Yt)
        self.AllModelObj.append(FullConn)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        top = CNN.creat_window("全连接参数配置")
        num_outputs_entry = CNN.create_input_box(top, "全连接输出维度：", 10)
        name = '全连接' + str(i)
        def get_input():
            global Listinstance
            # 从下拉框中获取输出维度
            num = int(num_outputs_entry.get())
            # 复制对象的标签
            self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
            self.AllModelObj[-1].ParaString.append(num)

            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
        CNN.new_element_bind(newitem)

    def nonlinear(self, event):
        i, x, y, newitem = self.element_adjustment(event, '非线性', 4)

        Nonline = Nonline_Class("Nonline" + str(i), 5, "非线性函数" + str(i), [], x, CNN.Yt)
        self.AllModelObj.append(Nonline)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        top = CNN.creat_window('非线性参数配置')  # 创建弹出窗口
        # 创建一个下拉框，用于选择非线性类型
        nonlinear_mode_combobox = CNN.create_dropdown_box(top, "非线性类型", ["Sigmoid", "ReLU", "Tanh"], "Sigmoid")  # 创建一个下拉框，用于选择池化类型
        name = '非线性' + str(i)
        # 创建
        def get_input():
            global Listinstance
            # 从下拉框中获取池化类型
            nonlinear_mode = nonlinear_mode_combobox.get()
            # 复制对象的标签
            self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
            self.AllModelObj[-1].ParaString.append(nonlinear_mode)
            self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetNonLPara(self.AllModelObj[-1]))
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
        CNN.new_element_bind(newitem)

    def class_ification(self, event):
        i, x, y, newitem = self.element_adjustment(event, '分类', 5)

        Classifier = Classifier_Class("Classifier" + str(i), 6, "分类" + str(i), [], x, CNN.Yt)
        self.AllModelObj.append(Classifier)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        top = CNN.creat_window('分类参数配置')  # 创建弹出窗口
        # 创建一个下拉框，用于选择非线性类型
        class_box_combobox = CNN.create_input_box(top, "阈值：", 0.001)  # 创建一个下拉框，用于选择池化类型
        name = '输出分类' + str(i) + '结果'
        # 创建
        def get_input():
            # 从输入框中获取阈值
            threshold = float(class_box_combobox.get())
            # 复制对象的标签
            self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
            self.AllModelObj[-1].ParaString.append(threshold)
            self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetClassifyPara(self.AllModelObj[-1]))
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
        CNN.new_element_bind(newitem)

    def error_calculation(self, event):
        i, x, y, newitem = self.element_adjustment(event, '误差', 6)

        Error = Error_Class("Error" + str(i), 7, "误差计算" + str(i), [],  x, y)
        self.AllModelObj.append(Error)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        name = '误差' + str(i)
        # 创建弹出窗口
        top = CNN.creat_window('误差参数配置')
        # 创建一个下拉框，用于选择误差类型
        loss_type_combobox = CNN.create_dropdown_box(top, '误差类型', ["CEE", "MSE", "MAE"],"CEE")
        # 创建
        def get_input():
            # 从下拉框中获取池化类型
            loss_type = loss_type_combobox.get()
            # 复制对象的标签
            self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
            self.AllModelObj[-1].ParaString.append(loss_type)
            self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetErrorPara(self.AllModelObj[-1]))
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
        CNN.new_element_bind(newitem)

    def adjust_one(self, event):
        i, x, y, newitem = self.element_adjustment(event, '调整1', 7)

        AjConv = AjConv_Class("AjConv" + str(i), 8, "卷积调整" + str(i), [],  x, y)
        self.AllModelObj.append(AjConv)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        name = '调整1 ' + str(i)
        # 复制图元对象的标签
        self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
        CNN.new_element_bind(newitem)

    def adjust_two(self, event):
        i, x, y, newitem = self.element_adjustment(event, '调整2', 8)

        AjFullconn = AjFullconn_Class("AjFullconn" + str(i), 9, "全连接调整" + str(i), [], x, y)
        self.AllModelObj.append(AjFullconn)
        CNN.selection_index = len(self.AllModelObj) - 1  # 从左边把图元拖到右边，因为此时选择框在最新的图元上，所以把最新图元层数赋值
        CNN.selection_type = 1
        name = '调整2 ' + str(i)
        # 复制图元对象的标签
        self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
        CNN.new_element_bind(newitem)

    def adjust_three(self, event):  # 调整3
        i, x, y, newitem = self.element_adjustment(event, '调整3', 9)

        name = '调整3 ' + str(i)
        # 复制图元对象的标签
        self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
        CNN.new_element_bind(newitem)

    # 此函数将图元从左侧框复制到右侧框，返回同类图元编号与坐标
    def element_adjustment(self, event, name, img_index):
        '''
        Function: element_adjustment Description: This function copies the element from the left box to the right box and returns the number of the same type of element and its coordinates.
        Input:
        event: the event object
        regular: a boolean value indicating whether y is a fixed value
        img_index: the index of the image in the list
        name: the name of the element
        Output:
        i: the number of the same type of element
        x: the x-coordinate of the new element
        y: the y-coordinate of the new element
        '''
        x = event.x + CNN.Offset_x
        y = event.y + CNN.Offset_y
        # 复制一个图片，表示调整操作
        new_item = CNN.Viewcanvas.create_image(x, y, image=CNN.List_image[img_index])
        self.Second_Items[0].append([new_item, name])
        # 同类图元编号
        i = self.count_string(name) + 1
        # 添加图元名称
        self.Element.append(name + ' ' + str(i))
        # 恢复图元的原位置
        event.widget.coords(CNN.Dragged_item, CNN.Original_x, CNN.Original_y)
        # 将新图元的id和坐标添加到列表中
        self.Items1.append([new_item, x, y])  ######## 因为元组不能改变，把原来的元组换成了列表
        # 返回同类图元编号,与坐标
        return i, x, y, new_item

    def main_line(self, event, index):
        te = ['参数连接线', '主数据连接线']
        choose = [2, 1]
        if len(self.Element) > 2:
            starting, ending = self.closest_coordinate(event)
            top = CNN.creat_window(te[index])  # 创建弹出窗口
            # 创建一个下拉框，用于选择图源
            start_mode_combobox = CNN.create_dropdown_box(top, "起始图元", self.Element, self.Element[starting])
            end_mode_combobox = CNN.create_dropdown_box(top, "终点图元", self.Element, self.Element[ending])
            def get_input():  # 创建
                result1 = start_mode_combobox.get()  # 获取输入框的内容
                result2 = end_mode_combobox.get()
                start = self.Element.index(result1)
                end = self.Element.index(result2)
                starting = (self.Items1[start][1], self.Items1[start][2])
                ending = (self.Items1[end][1], self.Items1[end][2])
                linesID = self.conn_lines(starting, ending, index)
                self.linesIDs.append(linesID)
                start_name = Net.AllModelObj[start].ObjID
                end_name = Net.AllModelObj[end].ObjID
                self.AllModelConn.append(ModelConn(linesID, choose[index], start_name, end_name).output())
                top.destroy()  # 关闭窗口

            button = tk.Button(top, text="获取信息", command=get_input)
            button.pack()
        event.widget.coords(CNN.Dragged_item, CNN.Original_x, CNN.Original_y)  # 恢复图元的原位置

    def closest_coordinate(self, event):
        x = event.x + CNN.Offset_x
        y = event.y + CNN.Offset_y
        given_coord = (x, y)
        coords_list = []
        for item in self.Items1:
            coords_list.append((item[1], item[2]))
        # 初始化最小距离和对应的坐标
        min_distance1 = float('inf')
        min_distance2 = float('inf')
        closest_coord1 = None
        closest_coord2 = None
        # 遍历列表中的坐标，找到最接近的两个坐标
        for coord in coords_list:
            dist = math.sqrt((given_coord[0] - coord[0]) ** 2 + (given_coord[1] - coord[1]) ** 2)
            if dist < min_distance1:
                min_distance2 = min_distance1
                closest_coord2 = closest_coord1
                min_distance1 = dist
                closest_coord1 = coord
            elif dist < min_distance2:
                min_distance2 = dist
                closest_coord2 = coord
        coord1 = coords_list.index(closest_coord1)
        coord2 = coords_list.index(closest_coord2)
        return coord1, coord2
        # return min(coord1, coord2), max(coord1, coord2)

    def count_string(self, sub_string):
        """
        计算列表中包含指定子字符串的元素数量。
        Parameters:
        - sub_string (str): 要查找的子字符串。
        Returns:
        - int: 包含指定子字符串的元素数量。
        """
        count = 0
        for string in self.Element:
            if sub_string in string:
                count += 1
        return count

    def conn_lines(self, start, end, index):
        smooth = [False, True]
        width = [2, 4]
        if start[0] == end[0]:
            lines_id = CNN.Viewcanvas.create_line(start[0], start[1] + 30, end[0], end[1] - 30, arrow=tk.LAST,
                                   arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index], width=width[index])
        elif start[1] == end[1]:
            lines_id = CNN.Viewcanvas.create_line(start[0] + 30, start[1], end[0] - 30, end[1], arrow=tk.LAST,
                                   arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index], width=width[index])
        else:
            if abs(start[0] - end[0]) > abs(start[1] - end[1]):
                # 创建数据线箭头
                lines_id = CNN.Viewcanvas.create_line(start[0] - 15, start[1], end[0] + 30,end[1], arrow=tk.LAST,
                                       arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index],
                                       width=width[index])
            else:
                # 创建数据线箭头
                lines_id = CNN.Viewcanvas.create_line(start[0], start[1] - 20, end[0] + 30, end[1], arrow=tk.LAST,arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index],
                                       width=width[index])
        CNN.conn_bind(lines_id)
        return lines_id
    
    #############################################################################################################################
    def new_conn_lines(self, linesID, start, end, StartOREnd): # StarOREnd判断是起点改变还是终点改变 1：起点改变 2：终点改变
        # if start[0] == end[0]:
        #     CNN.Viewcanvas.coords(linesID, start[0], start[1] + 30, end[0], end[1] - 30)
        # elif start[1] == end[1]:
        #     CNN.Viewcanvas.coords(linesID, start[0] + 30, start[1], end[0] - 30, end[1])
        # else:
        #     if abs(start[0] - end[0]) > abs(start[1] - end[1]):
        #         # 创建数据线箭头
        #         CNN.Viewcanvas.coords(linesID, start[0] - 15, start[1], end[0] + 30,end[1])
        #     else:
        #         # 创建数据线箭头
        #         CNN.Viewcanvas.coords(linesID, start[0], start[1] - 20, end[0] + 30, end[1])
        CNN.Viewcanvas.coords(linesID, start[0], start[1], end[0], end[1])
    #############################################################################################################################
    def check_connection(self):
        obj_names = ['DataSet', 'Conv', 'Pool', 'FullConn',
                        'Nonline', 'Classifier', 'Error',
                        'AjConv', 'AjFullconn']
        if len(Net.AllModelConn) == 0:
            messagebox.showerror("错误", "请连接图元！")
            return
        # print(Net.AllModelConn)
        for model_conn in Net.AllModelConn:
            start_name = re.sub(r'\d+', '', model_conn[2])
            end_name = re.sub(r'\d+', '', model_conn[3])
            start = obj_names.index(start_name)
            end = obj_names.index(end_name)
            if self.RIGHT_MODEL[start][end] == 0:
                messagebox.showerror("错误", model_conn[2]+'与' + model_conn[3]+"连接不正确，请检查！")
                return
        messagebox.showinfo("成功", "连接正确，恭喜！")
        
    def delete_model_conn(self):
        if CNN.selection_type == 1:
            top = CNN.creat_window('警告')  # 创建弹出窗口
            top.geometry("200x200+600+300")
            def delete_obj():  # 创建
                index = CNN.selection_index
                del self.AllModelObj[index]
                del self.Element[index]
                CNN.Viewcanvas.delete(self.Items1[index][0])
                del self.Items1[index]
                CNN.Viewcanvas.delete(self.Second_Items[1][index][0])
                del self.Second_Items[0][index]
                del self.Second_Items[1][index]
                CNN.Viewcanvas.delete(CNN.selection_rect)
                CNN.selection_type = None
                CNN.selection_index = None
                top.destroy()  # 关闭窗口
            def close_window():
                top.destroy()
            label = tk.Label(top, text="确定删除选中的图元？", font=("Arial", 12))
            label.pack(pady=30)
            button1 = tk.Button(top, text="删除", command=delete_obj)
            button1.pack(side=tk.LEFT, padx=20)
            button2 = tk.Button(top, text="取消", command=close_window)
            button2.pack(side=tk.RIGHT, padx=20)
        elif CNN.selection_type == 2:
            top = CNN.creat_window('警告')  # 创建弹出窗口
            top.geometry("200x200+600+300")
            def delete_obj():  # 创建
                index = CNN.selection_index
                del self.AllModelConn[index]
                CNN.Viewcanvas.delete(self.linesIDs[index])
                del self.linesIDs[index]
                CNN.Viewcanvas.delete(CNN.selection_rect)
                top.destroy()  # 关闭窗口
                CNN.selection_type = None
                CNN.selection_index = None
            def close_window():
                top.destroy()
            label = tk.Label(top, text="确定删除选中的连线？", font=("Arial", 12))
            label.pack(pady=30)
            button1 = tk.Button(top, text="删除", command=delete_obj)
            button1.pack(side=tk.LEFT, padx=20)
            button2 = tk.Button(top, text="取消", command=close_window)
            button2.pack(side=tk.RIGHT, padx=20)
            
    
    def connecting_lines(self, obj_x, obj_y, text, text_record, image):
        CNN.Viewcanvas.create_image(obj_x, obj_y, image=image)  # 创建图元对象
        CNN.Viewcanvas.create_text(obj_x + text_record[0], obj_y + text_record[1], text=text, font=("黑体", 14))  # 创建图元对象的标签

    def creating_elements(self):
        text_record = [(0, -50), (0, 50), (-80, 0)]
        # 遍历AllModelObj列表，在窗口左侧创建图元菜单
        for obj in self.AllModelObj:
            # 并且要根据需求调整每个对象的位置
            obj_x = obj.ObjX  # 根据对象的id计算x坐标
            obj_y = obj.ObjY # 根据对象的id计算y坐标
            self.Item_Record.append((obj_x, obj_y))
            self.Item_Name.append(obj.ObjID)
            # 根据对象的类型，绘制相应的图形
            if 'Error' in obj.ObjID:
                self.connecting_lines(obj_x, obj_y, obj.ObjLable, text_record[0], CNN.List_image[obj.ObjType - 1])
            elif 'Aj' in obj.ObjID:
                self.connecting_lines(obj_x, obj_y, obj.ObjLable, text_record[2], CNN.List_image[-1])
            else:
                self.connecting_lines(obj_x, obj_y, obj.ObjLable, text_record[1], CNN.List_image[obj.ObjType - 1])

    def ligature(self):  # 连接线
        # print(Item_Record)
        for conn in self.AllModelConn:
            starting = self.Item_Name.index(conn[2])
            # print(starting)
            ending = self.Item_Name.index(conn[3])
            if conn[1] == 1:
                # print(Item_Record[starting])
                self.conn_lines(self.Item_Record[starting], self.Item_Record[ending], 1)
            else:
                self.conn_lines(self.Item_Record[starting], self.Item_Record[ending], 0)
    def setpara_func(self):
        ObjIndex = CNN.selection_index
        if self.AllModelObj[ObjIndex].ObjType == 2:   # 卷积
            self.modify_conv(ObjIndex)
        elif self.AllModelObj[ObjIndex].ObjType == 3:  # 池化层
            self.modify_pooling(ObjIndex)
        elif self.AllModelObj[ObjIndex].ObjType == 4:  # 全连接层
            self.modify_fully(ObjIndex)
        elif self.AllModelObj[ObjIndex].ObjType == 5:  # 非线性层
            self.modify_nonlinear(ObjIndex)
        elif self.AllModelObj[ObjIndex].ObjType == 6:   #分类层
            self.modify_class(ObjIndex)
        elif self.AllModelObj[ObjIndex].ObjType == 7:  # 误差
            self.modify_error(ObjIndex)
    def query(self, name):
        
        letters = ''.join([char for char in name if char.isalpha()])
        numbers = ''.join([char for char in name if char.isdigit()])
        
        text_name = letters + ' ' + str(numbers)
        text_id = None
        
        for i, sublist in enumerate(self.Second_Items[1]):
            for j, element in enumerate(sublist):
                if element == name:
                    text_id = self.Second_Items[1][i][0]
                    break
            if text_id is not None:
                break
        return text_name, text_id

    def modify_conv(self, ObjIndex):
        """
        右框修改卷积图元参数及AllModelObj参数
        Parameters: None
        Returns: None
        """
        top = CNN.creat_window('卷积参数配置')  # 创建弹出窗口
        kernel_size_entry = CNN.create_input_box(top, "卷积核大小：", 3)  # 创建一个输入框，获取卷积核大小
        stride_entry = CNN.create_input_box(top, "卷积步长：", 1)  # 创建一个输入框，获取卷积步长
        name = self.AllModelObj[ObjIndex].ObjLable
        textname, textid = self.query(name)
        textname = textname + ':'
        def get_input():  # 创建
            # 获取输入框的内容
            result1 = int(kernel_size_entry.get())
            result2 = int(stride_entry.get())
            text = str(result1) + "*" + str(result2)
            CNN.Viewcanvas.itemconfig(textid, text=textname+text)
            self.AllModelObj[ObjIndex].ParaString[0] = [result1, result2]
            self.AllModelObj[ObjIndex].ParaString[1] = self.AllModelObj[ObjIndex].SetConvPara(self.AllModelObj[ObjIndex])
            top.destroy()  # 关闭窗口
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()

    def modify_pooling(self, ObjIndex):
        top = CNN.creat_window("池化参数配置")
        pool_type_combobox = CNN.create_dropdown_box(top, "池化池类型：", ["max", "avg", "min"], 'max')  # 创建一个下拉框，用于选择池化类型
        pool_size_entry = CNN.create_input_box(top, "池化池大小：", 2)  # 创建一个输入框，用于输入池大小
        name = self.AllModelObj[ObjIndex].ObjLable
        textname, textid = self.query(name)
        textname = textname + ':'
        # 创建
        def get_input():
            # 从下拉框中获取池化类型
            pool_type = pool_type_combobox.get()
            # 从输入框中获取池大小
            pool_size = int(pool_size_entry.get())
            text = str(pool_size) + "*" + str(pool_size) + str(pool_type)
            CNN.Viewcanvas.itemconfig(textid, text=textname+text)
            self.AllModelObj[ObjIndex].ParaString[0] = [pool_type, pool_size]
            self.AllModelObj[ObjIndex].ParaString[1] = self.AllModelObj[ObjIndex].SetPollPara(self.AllModelObj[ObjIndex])
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
    
    def modify_fully(self, ObjIndex):
        top = CNN.creat_window("全连接参数配置")
        num_outputs_entry = CNN.create_input_box(top, "全连接输出维度：", 10)
        def get_input():
            global Listinstance
            # 从下拉框中获取输出维度
            num = int(num_outputs_entry.get())
            self.AllModelObj[ObjIndex].ParaString = num
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
    
    def modify_nonlinear(self, ObjIndex):
        top = CNN.creat_window('非线性参数配置')  # 创建弹出窗口
        # 创建一个下拉框，用于选择非线性类型
        nonlinear_mode_combobox = CNN.create_dropdown_box(top, "非线性类型", ["Sigmoid", "ReLU", "Tanh"], "Sigmoid")  # 创建一个下拉框，用于选择池化类型
        # 创建
        def get_input():
            global Listinstance
            # 从下拉框中获取池化类型
            nonlinear_mode = nonlinear_mode_combobox.get()
            self.AllModelObj[ObjIndex].ParaString[0] = nonlinear_mode
            self.AllModelObj[ObjIndex].ParaString[1] = self.AllModelObj[ObjIndex].SetNonLPara(self.AllModelObj[ObjIndex])
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
    
    def modify_class(self, ObjIndex):
        top = CNN.creat_window('分类参数配置')  # 创建弹出窗口
        # 创建一个下拉框，用于选择非线性类型
        class_box_combobox = CNN.create_input_box(top, "阈值：", 0.001)  # 创建一个下拉框，用于选择池化类型
        # 创建
        def get_input():
            # 从输入框中获取阈值
            threshold = float(class_box_combobox.get())
            self.AllModelObj[ObjIndex].ParaString[0] = threshold
            self.AllModelObj[ObjIndex].ParaString[1] = self.AllModelObj[ObjIndex].SetClassifyPara(self.AllModelObj[ObjIndex])
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()
    
    def modify_error(self, ObjIndex):
        # 创建弹出窗口
        top = CNN.creat_window('误差参数配置')
        # 创建一个下拉框，用于选择误差类型
        loss_type_combobox = CNN.create_dropdown_box(top, '误差类型', ["CEE", "MSE", "MAE"],"CEE")
        # 创建
        def get_input():
            # 从下拉框中获取池化类型
            loss_type = loss_type_combobox.get()
            self.AllModelObj[ObjIndex].ParaString[0] = loss_type
            self.AllModelObj[ObjIndex].ParaString[1] = self.AllModelObj[ObjIndex].SetErrorPara(self.AllModelObj[ObjIndex])
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()

class ModelObj: # 网络对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        self.ObjID = ObjID  # 图元号
        self.ObjType = ObjType  # 图元类别
        self.ObjLable = ObjLable  # 对象标签
        self.ParaString = ParaString  # 参数字符串
        self.ObjX = ObjX  # 对象位置x坐标
        self.ObjY = ObjY  # 对象位置y坐标

class Data_Class(ModelObj):  # 数据集网络对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.LoadData = self.load_data  # 基本操作函数
        self.SetDataPara = self.set_data_para  # 参数设置函数

    # 定义加载数据集load_data()
    def load_data(self, DataPara):
        global SubFolders
        listimages = []  # 存储图片的列表
        list_path = []
        SubFolders, train_Path = read_folders(DataPara["train_imgPath"])
        list_path.append(train_Path)
        _, path_list = read_folders(DataPara["test_imgPath"])
        list_path.append(path_list)
        for data_path in list_path:
            images = []  # 存储图片的列表
            for path in data_path:
                # print(path)
                img = self.image_to_array(path, DataPara["img_width"], DataPara["img_height"])  # 读取图片数据
                img = img.T  # 转置，图像的行和列将互换位置。
                images.append(img)  # 将图片数组添加到训练集图片列表中
            listimages.append(np.array(images))  # 返回转换后的数组
        return listimages[0], listimages[1]

    def set_data_para(self):# 定义加载数据集的参数SetLoadData()
        # 设置数据集路径信息
        train_imgPath = filedialog.askdirectory(title="请选择训练集") + "/"  # 训练集文件夹的位置
        test_imgPath = filedialog.askdirectory(title="请选择测试集") + "/"  # 测试集文件夹的位置
        # 设置图片大小
        img_width = 48  # 图片宽度
        img_height = 48  # 图片高度
        # 设置每批次读入图片的数量
        batch_size = 32  # 批次大小
        # 返回DataPara参数，这里用一个字典来存储
        DataPara = {"train_imgPath": train_imgPath,
                    "test_imgPath": test_imgPath,
                    "img_width": img_width,
                    "img_height": img_height,
                    "batch_size": batch_size}
        return DataPara

    def images_statistics(self, folder_path):
        # 获取所有子文件夹路径
        subfolders = glob.glob(os.path.join(folder_path, '*'))
        # 遍历子文件夹并统计照片数量
        for subfolder in subfolders:
            if os.path.isdir(subfolder):
                subfolder_name = os.path.basename(subfolder)
                photos = glob.glob(os.path.join(subfolder, '*.jpg'))  # 假设照片文件是jpg格式，可根据实际情况修改
                print(f'类别 {subfolder_name} 中的照片数量：{len(photos)}')

    # 定义一个函数，实现图片转换为数组的功能
    def image_to_array(self, path, height, width):
        img = Image.open(path).convert("L")  # 转换为灰度模式
        img = img.resize((height, width))  # 将图片缩放为height*width的固定大小
        data = np.array(img)  # 将图片转换为数组格式
        data = data / 255.0  # 将数组中的数值归一化，除以255
        return data

class Conv_Class(ModelObj):  # 卷积对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.ConvProc = self.conv_proc  # 基本操作函数
        self.SetConvPara = self.setconv_para  # 参数设置函数
    # 定义卷积函数ConvProc()
    def conv_proc(self, image, ConvPara):
        # 获取输入数据的大小，这里假设是单通道的图片
        c, image_h, image_w = image.shape
        # 获取卷积核的大小和卷积核
        kernel_h = ConvPara["kernel_h"]
        kernel_w = ConvPara["kernel_w"]
        kernel = ConvPara["kernel"]
        # 计算输出数据的大小
        out_h = (image_h - kernel_h) // ConvPara["stride"] + 1
        out_w = (image_w - kernel_w) // ConvPara["stride"] + 1
        # 初始化输出数据为零矩阵
        output = np.zeros((c, out_h, out_w))
        # 对于每一个输出位置，进行卷积操作
        # 遍历每个通道
        for k in range(c):
            # 遍历每个输出位置
            for i in range(out_h):
                for j in range(out_w):
                    # 获得步长和偏置
                    stride = ConvPara["stride"]
                    bias = ConvPara["bias"]
                    # 计算卷积操作
                    output[k, i, j] = np.sum(
                        image[k, i * stride:i * stride + kernel_h, j * stride:j * stride + kernel_w] * kernel) + bias
        # 返回卷积计算后的数组（特征向量）
        return output

    def setconv_para(self, model_obj):# 定义设置卷积参数的函数SetConvPara()
        # 从Conv的ParaString得到一个包含所有卷积参数的列表
        conv_list = model_obj.ParaString[0]
        height, stride = conv_list[0], conv_list[1]
        height = int(height)  # 设置卷积核大小
        stride = int(stride)  # 设置步长
        # 卷积核为3x3的单位矩阵(高斯分布)，通常再乘上一个小的数，比如0.01
        kernel = np.random.randn(height, height) * 0.1
        padding = 0  # 设置填充，这里假设是0
        bias = 0.5  # 设置偏置，这里假设是0.5
        # 返回ConvPara参数,这里用一个字典来存储
        ConvPara = {"kernel": kernel,
                              "kernel_h": height,
                              "kernel_w": height,
                              "stride": stride,
                              "padding": padding,
                              "bias": bias}
        return ConvPara

class Pool_Class(ModelObj):  # 池化对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.PoolProc = self.pool_proc  # 基本操作函数
        self.SetPollPara = self.setpool_para  # 参数设置函数

    def pool_proc(self, image, PoolPara):
        pool_mode = PoolPara["pool_mode"]
        pool_size = PoolPara["pool_size"]
        stride = PoolPara["stride"]
        c, h, w = image.shape  # 获取输入特征图的高度和宽度
        out_h = int((h - pool_size) / stride) + 1  # 计算输出特征图的高度
        out_w = int((w - pool_size) / stride) + 1  # 计算输出特征图的宽度
        out = np.zeros((c, out_h, out_w))  # 初始化输出特征图为全零数组
        for k in range(c):  # 对于输出的每一个位置上计算：
            for i in range(out_h):
                for j in range(out_w):
                    window = image[k, i * stride:i * stride + pool_size, j * stride:j * stride + pool_size]
                    if pool_mode == "max":  # 最大池化
                        out[k][i][j] = np.max(window)
                    elif pool_mode == "avg":  # 平均池化
                        out[k][i][j] = np.mean(window)
                    elif pool_mode == "min":  # 最小池化
                        out[k][i][j] = np.min(window)
                    else:  # 无效的池化类型
                        raise ValueError("Invalid pooling mode")
        return out  # 返回特征图。

    # 定义设置池化参数的函数
    def setpool_para(self, model_obj):
        # 从Pool的ParaString得到一个包含所有池化参数的列表
        pool_list = model_obj.ParaString[0]
        # 池大小和池类型
        pool_mode, pool_size = pool_list[0], pool_list[1]
        pool_size = int(pool_size)
        stride = 2  # 设置步长，这里假设是2
        PoolPara = {"pool_mode": pool_mode,  # 返回PoolPara参数，这里用一个字典来存储
                              "pool_size": pool_size,
                              "stride": stride}
        return PoolPara  # 返回PoolPara参数

class FullConn_Class(ModelObj):  # 全连接对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.FullConnProc = self.fullconn_proc  # 基本操作函数
        self.SetFullConnPara = self.setfullconn_para  # 参数设置函数

    def fullconn_proc(self, inputdata, FullConnPara):
        weights = FullConnPara["weights"]  # 从FullConnPara参数中获取权重矩阵
        bias = FullConnPara["bias"]  # 偏置向量
        inputdata = inputdata.reshape(1, inputdata.shape[1] * inputdata.shape[2])  # 对输入进行展平处理，变换为单通道的一维数组格式
        output = np.dot(inputdata, weights.T) + bias  # 计算全连接层的线性变换：inputdata与权重矩阵w进行乘法，再加上偏置向量b
        return output  # 返回全连接计算后的数组

    # 定义一个函数来设置全连接层的相关参数，这里可以根据实际情况修改或随机生成
    def setfullconn_para(self, data, model_obj):
        # 从FullConn的ParaString得到一个包含所有全连接参数的列表
        num_outputs = model_obj.ParaString[0]
        # 设置权重矩阵及其输出维度
        num_outputs = int(num_outputs)
        # 获取池化后的图片数组的长度和宽度
        dim = len(data.shape)
        if dim == 2:
            height, width = data.shape
        elif dim == 3:
            c, height, width = data.shape
        num_outputs = num_outputs
        weights = np.random.randn(num_outputs, height * width)
        bias = np.random.randn(1, num_outputs)
        # 返回FullConnPara参数，这里用一个字典来存储
        FullConnPara = {"weights": weights, "bias": bias, "num_outputs": num_outputs}
        return FullConnPara

class Nonline_Class(ModelObj):  # 非线性对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.NonlinearProc = self.nonlinear_proc  # 基本操作函数
        self.SetNonLPara = self.setnonl_para  # 参数设置函数

    # 定义非线性函数
    def nonlinear_proc(self, inputdata, NonLPara):
        # 从NonLPara参数中获取非线性函数类型
        nonlinearmode = NonLPara["nonlinearmode"]
        # 判断nonlinearmode，进行相应的计算
        if nonlinearmode == "Sigmoid":
            # Sigmoid函数，将任何实数的输入映射到0和1之间的输出
            output = 1 / (1 + np.exp(-inputdata))
            # 求出Sigmoid函数的导数，用于全连接层权重矩阵和偏执向量的调整
            output_derivative = (1 / (1 + np.exp(-inputdata))) * (1 - (1 / (1 + np.exp(-inputdata))))
        elif nonlinearmode == "ReLU":
            # ReLU函数，将负数输入置为0，而正数输入保持不变
            output = np.maximum(inputdata, 0)
            # 求出ReLU函数的导数，用于全连接层权重矩阵和偏执向量的调整
            output_derivative = np.where(inputdata > 0, 1, 0)
        elif nonlinearmode == "Tanh":
            # Tanh函数，将任何实数的输入映射到-1和1之间的输出
            output = np.tanh(inputdata)
            # 求出Tanh函数的导数，用于全连接层权重矩阵和偏执向量的调整
            output_derivative = 1 - np.tanh(inputdata) ** 2
        else:
            # 非法的非线性类型，抛出异常
            raise ValueError("Invalid nonlinear mode")
        # 返回计算后的值
        return output, output_derivative

    # 定义设置非线性参数的函数
    def setnonl_para(self, model_obj):
        # 从Nonline的ParaString得到一个包含所有非线性参数的列表
        nonlinearmode = model_obj.ParaString[0]
        NonLPara = {"nonlinearmode": nonlinearmode}  # 返回NonLPara参数，这里用一个字典来存储
        return NonLPara  # 返回NonLPara参数

class Label:  #  标签
    # 设置标签类别列表并将其转化为one-hot向量的形式
    def setlabel_para(self, label_list):
        num_classes = len(label_list)
        identity_matrix = np.eye(num_classes)
        label_dict = {label: identity_matrix[i] for i, label in enumerate(label_list)}
        return label_dict
    # 读取样本数据集，遍历每个样本，并将样本和对应的标签组成元组，返回标记好标签的样本列表
    def label_proc(self, samples, labels, label_dict):
        labeled_samples = [(sample, label_dict[label]) for sample, label in zip(samples, labels)]
        return labeled_samples
    def label_array(self, i, batch):
        # 读取标签数据
        path_csv = Net.ImgPath[0].split('/')[-2] + '.csv'
        df = pd.read_csv(path_csv, header=None, skiprows=range(0, i * batch), nrows=(i + 1) * batch - i * batch)
        # print(df)
        # 将标签数据转化成数组
        right_label = df.iloc[:, 0].tolist()
        right_label = list(map(int, right_label))
        right_label = [x for x in right_label]
        # print(right_label)
        return right_label

class Classifier_Class(ModelObj):  # 分类对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.ClassifierProc = self.classifier_proc  # 基本操作函数
        self.SetClassifyPara = self.setclassify_para  # 参数设置函数

    def classifier_proc(self, inputdata, ClassifyPara):
        # 找到概率超过阈值的标签分类，就是分类结果
        # 假设输入数据是一个一维数组，表示每个类别的概率
        # 假设输出数据是一个整数，表示预测的类别编号
        # 如果没有任何类别的概率超过阈值，就返回-1表示无法分类
        # 定义softmax函数
        def softmax(x):
            # 减去最大值，防止数值溢出
            x -= np.max(x)
            # 计算指数和归一化
            return np.exp(x) / np.sum(np.exp(x))
        # 从ClassifyPara参数中获取阈值
        threshold = ClassifyPara["threshold"]
        # 调用softmax函数，得到概率分布向量
        prob = softmax(inputdata)
        # 如果概率高于阈值，就将该类别加入输出结果
        prob1 = prob[prob >= threshold]
        # 使用where()函数来返回等于概率最大值的元素的索引
        index = np.where(prob == max(prob1))
        # 使用item()方法来将索引转换为标准Python标量
        output = index[1].item(0) + 1
        # 返回分类标签
        return output

    # 定义设置分类函数参数的函数
    def setclassify_para(self, model_obj):
        # 设定阈值,可以根据你的数据和任务来调整阈值
        threshold = float(model_obj.ParaString[0])
        # 返回ClassifyPara参数，这里用一个字典来存储
        ClassifyPara = {"threshold": threshold}
        # 返回ClassifyPara参数
        return ClassifyPara

class Error_Class(ModelObj):  # 误差计算对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.ErrorProc = self.error_proc  # 基本操作函数
        self.SetErrorPara = self.seterror_para  # 参数设置函数

    def error_proc(self, input, label, ErrorPara):
        label_list, loss_type = ErrorPara  # 读取标签列表和损失函数类型
        one_hot_matrix = np.eye(len(label_list))  # 创建一个单位矩阵，大小为标签类别的个数
        index = [x for x in label]
        # print(label)
        label_one_hot = np.take(one_hot_matrix, index, axis=0)  # 从one-hot矩阵中取出对应的向量
        # print(label_one_hot)
        if loss_type == "CEE":  # 确定损失函数类别，实现不同的损失函数，计算输入值与label之间的误差
            # 使用交叉熵损失函数，公式为：-sum(label_one_hot * log(input)) / n
            loss = -np.sum(label_one_hot * np.log(input)) / len(input)
        elif loss_type == "MSE":
            # 使用均方误差损失函数，公式为：sum((input - label_one_hot) ** 2) / n
            loss = np.sum((input - label_one_hot) ** 2) / len(input)
        elif loss_type == "MAE":
            # 使用平均绝对误差损失函数，公式为：sum(abs(input - label_one_hot)) / n
            loss = np.sum(np.abs(input - label_one_hot)) / len(input)
        else:
            raise ValueError("Invalid loss type")  # 如果损失函数类型不在以上三种中，抛出异常
        return loss  # 返回误差值loss

    # 定义设置误差参数的函数
    def seterror_para(self, model_obj):
        label_list = [i for i in range(len(SubFolders))]# 确定参数信息: 标签类别，损失函数类型
        # 假设损失函数类型为交叉熵（Cross Entropy Error，CEE）
        # 从Error的ParaString得到
        loss_type = model_obj.ParaString[0]
        # 返回ErrorProc参数，这里用一个元组来存储
        ErrorPara = (label_list, loss_type)
        # 返回ErrorPara参数
        return ErrorPara

class AjConv_Class(ModelObj):  # 卷积调整对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.AjConvProc = self.ajconv_proc  # 基本操作函数
        self.SetAjConvPara = self.setajconv_para  # 参数设置函数

    def ajconv_proc(self, images, AjConvPara):
        kernel_grad_list = []
        bias_grad = 0  # 初始化偏置项梯度为零
        for c in images:
            # 计算卷积核和偏置项的梯度
            kernel_grad = np.zeros_like(AjConvPara['kernel_info'])  # 初始化卷积核梯度为零矩阵
            for i in range(AjConvPara['loss'].shape[0]):  # 遍历误差值矩阵的行
                for j in range(AjConvPara['loss'].shape[1]):  # 遍历误差值矩阵的列
                    # 将输入数据数组中对应的子矩阵旋转180度，与误差值相乘，累加到卷积核梯度上
                    kernel_grad += np.rot90(c[i:i + kernel_grad.shape[0], j:j + kernel_grad.shape[0]], 2) * AjConvPara['loss'][i, j]
                    # 将误差值累加到偏置项梯度上
                    bias_grad += AjConvPara['loss'][i, j]
            kernel_grad_list.append(kernel_grad)
        # 使用stack函数沿着第0个轴把一百个a数组堆叠起来
        result = np.stack(kernel_grad_list, axis=0)
        kernel_grad = np.sum(result, axis=0) / len(images)  # 沿着第0个维度求和
        # 更新卷积核和偏置项参数
        kernel = AjConvPara['kernel_info'] - AjConvPara['learning_rate'] * kernel_grad  # 卷积核参数减去学习率乘以卷积核梯度
        bias = AjConvPara['bias'] - AjConvPara['learning_rate'] * bias_grad  # 偏置项参数减去学习率乘以偏置项梯度
        return kernel, bias  # 返回更新后的卷积参数

    def setajconv_para(self, loss, ConvPara):
        # print(model_obj)
        # ConvPara = model_obj.ParaString[1]
        kernel = ConvPara['kernel']  # 卷积核信息
        bias = 0.5
        learning_rate = 0.01  # 学习率
        loss = np.array([[loss]])
        AjConvPara = {'kernel_info': kernel, 'bias': bias, 'learning_rate': learning_rate, 'loss': loss}
        return AjConvPara

class AjFullconn_Class(ModelObj):  # 全连接调整对象
    def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
        super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
        self.AjFullconnProc = self.ajfullconn_proc  # 基本操作函数
        self.SetAjFCPara = self.setajfc_para  # 参数设置函数

    def ajfullconn_proc(self, AjFCPara):
        # 根据激活函数的参数选择相应的函数和导数
        # 计算权重矩阵和偏置向量的梯度，使用链式法则
        gradient_weights = np.outer(AjFCPara['loss'], AjFCPara['learning_rate'])
        # 更新权重矩阵和偏置向量
        weight_matrix = AjFCPara['weights'] - gradient_weights
        bias_vector = AjFCPara['bias'] - AjFCPara['learning_rate'] * AjFCPara['bias']
        # 返回更新后的权重矩阵和偏置向量
        return weight_matrix, bias_vector

    def setajfc_para(self, loss, FullConnPara):
        # FullConnPara = model_obj.ParaString[1]
        weights = FullConnPara["weights"]
        bias = FullConnPara["bias"]
        loss = np.array([loss])
        AjFCPara = {
            'weights': weights,  # 全连接权重
            'bias': bias,  # 全连接偏置
            'learning_rate': 0.01,  # 学习率
            'loss': loss  # 误差值
        }
        return AjFCPara


class Model:
    def __init__(self):
        self.Batch_First = []  # 每个批次第一张数据
        self.Batch_First_Sheet = []  # 批次第一张数据
        self.Flage = 0  # 是否存在模型
        self.Train_list = []  # 存储训练设置参数
        self.Prob_images = []  # 存储分类的数据
    # def button_link(self):
    #     # 替每个主界面的按钮绑定各自的函数
    #     CNN.button['button1'].config(command=self.load_model)  # 加载已有模型
    #     CNN.button['button2'].config(command=CNN.restart_new_model)  # 新建模型
    #     CNN.button['button3'].config(command=Net.setpara_func)  # 图元参数设置
    #     CNN.button['button4'].config(command=self.save_model)  # 保存模型
    #     CNN.button['button5'].config(command=Net.check_connection)  # 模型正确性检查
    #     CNN.button['button6'].config(command=CNN.close_program)  # 退出
    #     CNN.button['button7'].config(command=self.train_setting)  # 训练参数
    #     CNN.button['button8'].config(command=self.train)  # 训练

    def train_setting(self):
        top = CNN.creat_window("训练参数配置")  # 创建弹出的参数配置窗口
        epoch_entry = CNN.create_input_box(top, "训练轮数：", 4)  # 创建一个输入框，获取训练轮数
        rate_entry = CNN.create_input_box(top, '学习率：', 0.1)  # 创建一个输入框，获取学习率
        batch_size_entry = CNN.create_input_box(top, "批次：", 32)  # 创建一个输入框，获取批次
        # 创建
        def get_input():
            # 获取输入框的内容
            result1 = int(epoch_entry.get())
            result2 = float(rate_entry.get())
            result3 = int(batch_size_entry.get())
            self.Train_list.append(result1)
            self.Train_list.append(result2)
            self.Train_list.append(result3)
            # 关闭窗口
            top.destroy()
        button = tk.Button(top, text="获取信息", command=get_input)
        button.pack()

    def determine(self):
        if len(self.Train_list) == 0:
            messagebox.showerror("错误", "请设置训练参数！")
            return 0
        if len(Net.AllModelObj) == 0:
            messagebox.showerror("错误", "请先建立网络！")
            return 0
        return 1

    def train(self):  # 训练
        if self.determine() == 0:
            return
        epoch, rate, batch_size = self.Train_list
        first_epoch = 1
        LabelPara = Label()
        count = 0
        # 查询误差对象位置
        for modelobj in Net.AllModelObj:
            # 如果是误差层，结束
            if modelobj.ObjType == 7:
                break
            count += 1
        ErrorPara = Net.AllModelObj[count].ParaString[1]
        while first_epoch <= epoch:
            first_epoch = first_epoch + 1
            for i in range(len(Net.Train_images) // batch_size):
                images = Net.Train_images[i * batch_size:(i + 1) * batch_size]
                self.forward_propagation(images) # 前向传播
                prob_images = np.squeeze(self.Prob_images)  # 获取分类结果的概率向量数组
                batch = len(prob_images)
                right_label = LabelPara.label_array(i, batch)  # 读取标签数据,将标签数据转化成数组
                loss = Net.AllModelObj[count].ErrorProc(prob_images, right_label, ErrorPara) * 0.1  # 计算误差值
                print("当前epoch:", first_epoch, i * batch_size, ":", (i + 1) * batch_size, "误差值：", loss)
                print("")
                self.back_propagation(loss, images) # 反向传播
        self.Flage = 1
        # 删除指定目录下的所有文件
        self.delete_all_files("test_category/")
        # 遍历训练图像数据集
        for i in range(len(Net.Train_images) // batch_size):
            # 从训练图像数据集中获取当前批次的图像
            images = Net.Train_images[i * batch_size:(i + 1) * batch_size]
            # 进行前向传播，获取图像结果
            image_results = self.forward_propagation(images)
            # 打印图像结果
            print(image_results)
            # 遍历每个图像结果
            for j in range(len(image_results)):
                # 将图像保存到指定目录的子文件夹中
                self.save_images_to_folders("test_category/", SubFolders[int(image_results[j]) - 1], images[j])

    def delete_all_files(self, folder_path):
        # 遍历文件夹中的所有子文件和子文件夹
        for root, dirs, files in os.walk(folder_path, topdown=False):
            for file_name in files:
                file_path = os.path.join(root, file_name)
                os.remove(file_path)  # 删除文件
            for dir_name in dirs:
                dir_path = os.path.join(root, dir_name)
                shutil.rmtree(dir_path)  # 递归删除文件夹

    def save_images_to_folders(self, folder_path, number, image_data):
        sub_folder_path = os.path.join(folder_path, number)
        # 检查数字子文件夹是否存在，若不存在则创建
        if not os.path.exists(sub_folder_path):
            os.makedirs(sub_folder_path)
        # 创建并保存图像到相应的子文件夹中
        image = Image.fromarray((image_data * 255).astype(np.uint8))
        # 顺时针旋转90度
        rotated_image = image.rotate(-90)
        # 获取数字子文件夹中已有的照片数量
        existing_images = os.listdir(sub_folder_path)
        image_count = len(existing_images)
        image_path = os.path.join(sub_folder_path, str(image_count + 1) + ".jpg")
        rotated_image.save(image_path)
    # 前向传播
    def forward_propagation(self, images):
        outputs = images  # 存储每一层的输出结果的列表
        data_flow = self.flow()  # 网络数据流列表
        for modelobj in Net.AllModelObj:
            # 如果是卷积层，就调用ConvProc函数，根据卷积核和步长进行卷积计算，并更新输出结果
            if modelobj.ObjType == 2:
                outputs = self.conn_forward(modelobj, outputs)
            # 如果是池化层，就调用PoolProc函数，进行池化计算，并更新输出结果
            elif modelobj.ObjType == 3:
                outputs = self.pool_forward(modelobj, outputs)
            # 如果是全连接层，就调用FullConnProc函数，根据权重矩阵和偏置向量进行全连接计算，并更新输出结果
            elif modelobj.ObjType == 4:
                outputs = self.fullconn_forward(modelobj, outputs)
            # 如果是非线性层，就调用NonlinearProc函数，根据激活函数进行非线性变换，并更新输出结果
            elif modelobj.ObjType == 5:
                outputs = self.nonlinear_forward(modelobj, outputs)
            # 如果是分类层，就调用ClassifierProc函数，根据权重矩阵和偏置向量进行分类计算，并更新输出结果
            elif modelobj.ObjType == 6:
                outputs = self.classifier_forward(modelobj, outputs)
        outputs = np.array(outputs)  # 将输出结果列表转换为数组形式，方便后续处理
        return outputs  # 返回输出结果数组
    # 这里是需要连线才使用
    # 前向传播
    # def forward_propagation(self, images):
    #     outputs = images  # 存储每一层的输出结果的列表
    #     data_flow = self.flow()  # 网络数据流列表
    #     for model_conn in data_flow:
    #         obj_count = self.query_obj(model_conn)
    #         modelobj = Net.AllModelObj[obj_count]
    #         # 如果是卷积层，就调用ConvProc函数，根据卷积核和步长进行卷积计算，并更新输出结果
    #         if modelobj.ObjType == 2:
    #             outputs = self.conn_forward(modelobj, outputs)
    #         # 如果是池化层，就调用PoolProc函数，进行池化计算，并更新输出结果
    #         elif modelobj.ObjType == 3:
    #             outputs = self.pool_forward(modelobj, outputs)
    #         # 如果是全连接层，就调用FullConnProc函数，根据权重矩阵和偏置向量进行全连接计算，并更新输出结果
    #         elif modelobj.ObjType == 4:
    #             outputs = self.fullconn_forward(modelobj, outputs)
    #         # 如果是非线性层，就调用NonlinearProc函数，根据激活函数进行非线性变换，并更新输出结果
    #         elif modelobj.ObjType == 5:
    #             outputs = self.nonlinear_forward(modelobj, outputs)
    #         # 如果是分类层，就调用ClassifierProc函数，根据权重矩阵和偏置向量进行分类计算，并更新输出结果
    #         elif modelobj.ObjType == 6:
    #             outputs = self.classifier_forward(modelobj, outputs)
    #
    #     outputs = np.array(outputs)  # 将输出结果列表转换为数组形式，方便后续处理
    #     return outputs  # 返回输出结果数组

    def flow(self):
        """
        构建网络数据流列表。
        Returns:
        - list: 包含网络数据流的列表。
        """
        data_flow = []  # 网络数据流列表
        # 定义可能出现在数据流中的前向模块名称
        forward_name = ['Conv', 'Pool', 'FullConn', 'Nonline', 'Classifier']
        # 遍历所有模型连接
        for model_conn in Net.AllModelConn:
            start_data = re.sub(r'\d+', '', model_conn[2])  # 获取连接的起始模型名称（去除数字部分）
            # 如果起始数据是 'DataSet'，将连接的目标数据添加到数据流中
            if start_data == 'DataSet':
                data_flow.append(model_conn[3])
            # 如果起始数据是前向模块名称之一
            if start_data in forward_name:
                # 如果数据流的最后一个元素与连接的起始模型名称相同，将连接的目标数据添加到数据流中
                if data_flow[-1] == model_conn[2]:
                    data_flow.append(model_conn[3])
        return data_flow

    def query_obj(self, conn_name):
        count = -1
        for model_obj in Net.AllModelObj:
            count += 1
            if model_obj.ObjID == conn_name:
                break
        return count

    def conn_forward(self, modelobj, outputs):
        ConvPara = modelobj.ParaString[1]
        # 初始化一个空列表来存储池化后的结果
        new_outputs = []
        # 对每一张图片进行前向传播
        i = 0
        for output in outputs:
            # 获取矩阵的维度
            dim = len(output.shape)
            # 如果是二维矩阵,则转化为三维矩阵
            if dim == 2:
                image_h, image_w = output.shape
                output = np.reshape(output, (1, image_h, image_w))
                # 调用ConvProc()函数，根据ConvPara参数完成卷积计算
                output = modelobj.ConvProc(output, ConvPara)
                new_outputs.append(output)
                if i == 0:
                    i += 1
                    self.Batch_First.append(output)
            # 若为三维矩阵，则保持不变直接卷积处理
            elif dim == 3:
                output = modelobj.ConvProc(output, ConvPara)
                new_outputs.append(output)
                if i == 0:
                    i += 1
                    self.Batch_First.append(output)
        # 将new_outputs赋值给outputs
        outputs = new_outputs
        # 只打印第一张图片信息，检查是否正确计算
        print("卷积处理结果：")
        print(outputs[0])
        print("")
        return outputs
    def pool_forward(self, modelobj, outputs):
        PoolPara = modelobj.ParaString[1]
        # 初始化一个空列表来存储池化后的结果
        new_outputs = []
        # 对outputs列表中的每个元素调用PoolProc函数，并将结果添加到new_outputs列表中
        i = 0
        for output in outputs:
            output = modelobj.PoolProc(output, PoolPara)
            new_outputs.append(output)
            if i == 0:
                i += 1
                self.Batch_First.append(output)
        # 将new_outputs赋值给outputs
        outputs = new_outputs
        print("池化处理结果：")
        print(outputs[0])
        print("")
        return outputs
    def fullconn_forward(self, modelobj, outputs):
        # 判断FullConn全连接是否存在参数
        if len(modelobj.ParaString) < 2:
            FullConnPara = modelobj.SetFullConnPara(outputs[0], modelobj)
            no = self.query_obj(modelobj.ObjID)
            Net.AllModelObj[no].ParaString.append(FullConnPara)
        else:
            FullConnPara = modelobj.ParaString[1]
        # 初始化一个空列表来存储池化后的结果
        new_outputs = []
        i = 0
        # 对outputs列表中的每个元素调用FullConnProc函数，并将结果添加到new_outputs列表中
        for output in outputs:
            output = modelobj.FullConnProc(output, FullConnPara)
            new_outputs.append(output)
            if i == 0:
                i += 1
                self.Batch_First.append(output)
        # 将new_outputs赋值给outputs
        outputs = new_outputs
        # 只打印第一张图片信息，检查是否正确计算
        print("全连接处理结果：")
        print(outputs[0])
        print("")
        return outputs
    def nonlinear_forward(self, modelobj, outputs):
        # 调用设置非线性参数的函数，获取非线性参数
        NonLPara = modelobj.ParaString[1]
        # 初始化一个空列表来存储池化后的结果
        new_outputs = []
        # 对outputs列表中的每个元素调用NonlinearProc函数，并将结果添加到new_outputs列表中
        i = 0
        for output in outputs:
            output, output_derivative = modelobj.NonlinearProc(output, NonLPara)
            new_outputs.append(output)
            if i == 0:
                i += 1
                self.Batch_First.append(output)
        # 将new_outputs赋值给outputs
        outputs = new_outputs
        # 只打印第一张图片信息，检查是否正确计算
        print("非线性处理结果：")
        print(outputs[0])
        print("")
        return outputs

    def classifier_forward(self, modelobj, outputs):
        # 设置分类函数参数
        ClassifyPara = modelobj.ParaString[1]
        # 初始化一个空列表来存储池化后的结果
        new_outputs = []
        self.Prob_images = []

        # 定义softmax函数
        def softmax(x):
            # 减去最大值，防止数值溢出
            x -= np.max(x)
            # 计算指数和归一化
            return np.exp(x) / np.sum(np.exp(x))

        # 对outputs列表中的每个元素调用ClassifierProc函数，并将结果添加到new_outputs列表中
        i = 0
        for output in outputs:
            outputx = modelobj.ClassifierProc(output, ClassifyPara)
            new_outputs.append(outputx)
            # 调用softmax函数，得到概率分布向量
            prob = softmax(output)
            # 将概率向量结果存储到列表
            self.Prob_images.append(prob)
            if i == 0:
                i += 1
                self.Batch_First.append(outputx)
        # 将new_outputs赋值给outputs
        outputs = new_outputs
        # 只打印第一张图片信息，检查是否正确计算
        print("输出示例概率向量：")
        print(self.Prob_images[0:3])
        print("")
        return outputs

    def back_propagation(self, loss, images):
        for modelobj in Net.AllModelObj:
            if modelobj.ObjType == 8:
                self.conv_propagation('Conv1', loss, images)
            if modelobj.ObjType == 9:
                self.full_propagation('FullConn1', loss)
    # 这里是需要连线才使用
    # def back_propagation(self, loss, images):
    #     data_back = []  # 网络反向传播列表
    #     for model_conn in Net.data_flow:
    #         forword_name = ['AjConv', 'AjFullconn']
    #         start_data = re.sub(r'\d+', '', model_conn[2])
    #         if start_data == 'Error':
    #             data_back.append([model_conn[3]])
    #         if start_data in forword_name:
    #             for i in range(len(data_back)):
    #                 if data_back[i][-1] == model_conn[2]:
    #                     data_back[i].append(model_conn[3])
    #     for i in data_back:
    #         if 'AjConv' in i[0]:
    #             self.conv_propagation(i[1], loss, images)
    #         if 'AjFullconn' in i[0]:
    #             self.full_propagation(i[1], loss)
    def conv_propagation(self, conv_name, loss, images):
        positioning = 0
        for i in range(len(Net.AllModelObj)):
            if Net.AllModelObj[i].ObjID == conv_name:
                positioning = i
            if Net.AllModelObj[i].ObjType == 8:
                modelobj = Net.AllModelObj[i]
                ConvPara = Net.AllModelObj[positioning].ParaString[1]
                AjConvPara = modelobj.SetAjConvPara(loss, ConvPara)
                kernel, bias = modelobj.AjConvProc(images, AjConvPara)
                Net.AllModelObj[positioning].ParaString[1]['kernel']  = kernel
                Net.AllModelObj[positioning].ParaString[1]['bias'] = bias
                break

    def full_propagation(self, full_name, loss):
        # 初始化变量 positioning 为 0，用于记录 full_name 在 Net.AllModelObj 中的位置
        positioning = 0
        # 遍历 Net.AllModelObj 列表
        for i in range(len(Net.AllModelObj)):
            # 查找 full_name 在 Net.AllModelObj 中的位置
            if Net.AllModelObj[i].ObjID == full_name:
                positioning = i
            if Net.AllModelObj[i].ObjType == 9:
                # 获取当前模型对象
                modelobj = Net.AllModelObj[i]
                # 获取当前模型对象的参数字符串（FullConnPara 是参数的一部分）
                FullConnPara = Net.AllModelObj[positioning].ParaString[1]
                # 调用 SetAjFCPara 方法设置特定类型模型对象的参数
                AjFCPara = modelobj.SetAjFCPara(loss, FullConnPara)
                # 调用 AjFullconnProc 方法计算权重和偏置
                weight, bias = modelobj.AjFullconnProc(AjFCPara)
                # 将计算得到的权重和偏置更新到 Net.AllModelObj 中的相应位置
                Net.AllModelObj[positioning].ParaString[1]['weights'] = weight
                Net.AllModelObj[positioning].ParaString[1]['bias'] = bias
                break

    def save_model(self):  # 保存网络模型
        if len(Net.AllModelConn) == 0:
            messagebox.showerror("错误", "请连接图元！")
            return
        if len(Net.AllModelObj) == 0:
            messagebox.showerror("错误", "请添加图元！")
            return
        if self.Flage==0:
            messagebox.showerror("错误", "请先训练模型！")
            return
        Path = filedialog.askdirectory(title="请选择模型保存") + "/"  # 训练集文件夹的位置
        file_path = Path + 'AllModelConn.pkl'
        # 检查文件是否存在
        if os.path.exists(file_path):
            # 删除文件
            os.remove(file_path)
        with open(file_path, "wb") as f:
            pickle.dump(Net.AllModelConn, f)  # 保存模型
        file_path = Path + 'AllModelObj.pkl'
        # 检查文件是否存在
        if os.path.exists(file_path):
            # 删除文件
            os.remove(file_path)
        with open(file_path, "wb") as f:
            pickle.dump(Net.AllModelObj, f)  # 保存模型
        # # 生成试验数据集，用于保存模型
        # test_images = np.random.rand(100, 48, 48) * 0.01
        # # 对试验数据集进行前向传播的计算
        # print(forward_propagation(test_images))
        # # 打开一个本地文件，用w模式或者wb模式

        # # 遍历网络结构
        # for layer in items3[1:]:
        #     count_model(layer)
        # messagebox.showinfo("成功", "保存正确，恭喜！")
    def load_model(self):
        # 弹出文件对话框，允许用户选择.pkl文件
        file_path1 = filedialog.askopenfilename(filetypes=[("选择网络对象总表", "*.pkl")])
        # 判断用户是否选择了文件
        if file_path1:
            # 读取.pkl文件内容
            with open(file_path1, 'rb') as file:
                Net.AllModelObj = pickle.load(file)
        print(Net.AllModelObj)
        # 弹出文件对话框，允许用户选择.pkl文件
        file_path2 = filedialog.askopenfilename(filetypes=[("网络连接对象总表", "*.pkl")])
        # 判断用户是否选择了文件
        if file_path2:
            # 读取.pkl文件内容
            with open(file_path2, 'rb') as file:
                Net.AllModelConn = pickle.load(file)
            print(Net.AllModelConn)
        Net.creating_elements()
        Net.ligature()

if __name__ == '__main__':
    CNN = Interactive_CNN()
    Net = Networking()
    Mod = Model()
    CNN.create_left_element(CNN.List_image)
    CNN.button_link()
    sys.stdout.write = CNN.redirector
    CNN.Root.mainloop()
