# -*- encoding: utf-8 -*-
'''
@File    :   X1_new.py
@License :   (C)Copyright 2018-2022

@Modify Time      @Author    @Version    @Desciption
------------      -------    --------    -----------
2023/6/30 15:51   zart20      1.0         None
'''
'''
【编程16.1】编制程序：对卷积神经网络模型的数据结构赋值
【目的及编程说明】读者通过编程16.1可理解可视化构建卷积神经网络模型相关的数据结构及其作用。这里给出的数据结构，
能够支持16.2及16.4节的输出界面，即能够绘制出一张卷积神经网络结构图。编程16.1要求为模型对象总表AllModelObj
和AllModelConn赋值。要求包含所有类型的图元对象（见下表述），并建立适当的连接关系。
网络对象总表AllModelObj(ObjID，ObjType，ObjLable，ProcFunc, SetParaFunc, ParaString, ObjX，ObjY)，
其中：ObjID 表示图元号；ObjType表示图元类别（1—数据集，2—卷积，3—池化，4—全连接，5—非线性，6—分类，7—误差计算，
8—卷积调整，9—全连接调整）；ObjLable为对象标签；ProcFunc为该图元被执行时的基本操作函数； 
SetParaFunc为给基本操作函数赋参数值的函数；ParaString 为同类不同图元的参数，ObjX, 
ObjY为对象在仿真输出屏幕中的位置（示例中未给出，由图形界面中的位置产生）。
'''
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  # 参数设置函数

    def load_data(self, DataPara):
        pass  # X3具体实现

    def set_data_para(self):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.LoadData,
                  self.SetDataPara, self.ParaString, self.ObjX, self.ObjY]
        return result
# if __name__ == '__main__':
#     DataSet = Data_Class("DataSet1", 1, "数据集1", ".", 120, 330)
#     print(DataSet)
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  # 参数设置函数

    def conv_proc(self, image, ConvPara):
        pass  # X3具体实现

    def setconv_para(self):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.ConvProc, self.SetConvPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result


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

    def pool_proc(self, image, PoolPara):
        pass  # X3具体实现

    def setpool_para(self):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.MaxPoolProc, self.SetPollPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result
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):
        pass  # X3具体实现

    def setfullconn_para(self, data):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.FullConnProc, self.SetFullConnPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result

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):
        pass  # X3具体实现

    def setnonl_para(self):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.NonlinearProc, self.SetNonLPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result

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):
        pass  # X3具体实现

    def setclassify_para(self):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.ClassifierProc, self.SetClassifyPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result

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):
        pass  # X3具体实现

    def seterror_para(self):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.ErrorProc, self.SetErrorPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result

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, input, AjConvPara):
        pass  # X3具体实现

    def setajconv_para(self):
        pass  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.AjConvProc, self.SetAjConvPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result

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):
        pass  # X3具体实现

    def setajfc_para(self, AjFCPara):
        print(AjFCPara)  # X3具体实现

    def output(self):  # 输出方法
        # 创建一个空列表
        result = [self.ObjID, self.ObjType, self.ObjLable, self.AjFullconnProc, self.SetAjFCPara, self.ParaString, self.ObjX,
                  self.ObjY]
        return result
# AjFullconn = AjFullconn_Class("AjFullconn", 9,
#                               "全连接调整1",  [], 510, 120)
# AjFullconn.SetAjFCPara('rrr')
DataSet = Data_Class("DataSet1", 1, "数据集1", [], 120, 330).output()
Conv = Conv_Class("Conv1", 2, "卷积1", [], 250, 330).output()
Pool = Pool_Class("Pool1", 3, "最大池化1", [], 380, 330).output()
FullConn = FullConn_Class("FullConn1", 4, "全连接1", [], 510, 330).output()
Nonline = Nonline_Class("Nonline1", 5, "非线性函数1", [], 640, 330).output()
Classifier = Classifier_Class("Classifier1", 6, "分类1", [], 780, 330).output()
Error = Error_Class("Error1", 7, "误差计算1", [], 710, 124).output()
AjConv = AjConv_Class("AjConv1", 8, "卷积调整1", [], 250, 70).output()
AjFullconn = AjFullconn_Class("AjFullconn1", 9,
                              "全连接调整1",  [], 510, 120).output()
AllModelObj = [DataSet, Conv, Pool, FullConn, Nonline, Classifier, Error, AjConv, AjFullconn]
# AjFullconn.SetAjFCPara(7)
# AjFullconn.SetAjFCPara(7)
# print(AllModelObj)
# 定义网络连接对象类
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
# if __name__ == '__main__':
#     ···AllModelObj方法继承
#     # 创建连接对象实例
#     Line1 = ModelConn(1, 1, DataSet.ObjID, Conv.ObjID)
#     Line2 = ModelConn(2, 1, Conv.ObjID, Pool.ObjID)
#     Line3 = ModelConn(3, 1, Pool.ObjID, FullConn.ObjID)
#     Line4 = ModelConn(4, 1, FullConn.ObjID, Nonline.ObjID)
#     Line5 = ModelConn(5, 1, Nonline.ObjID, Classifier.ObjID)
#     Line6 = ModelConn(6, 1, Classifier.ObjID, Error.ObjID)
#     Line7 = ModelConn(7, 2, Error.ObjID, AjFullconn.ObjID)
#     Line8 = ModelConn(8, 2, Error.ObjID, AjConv.ObjID)
#     Line9 = ModelConn(9, 2, AjFullconn.ObjID, FullConn.ObjID)
#     Line10 = ModelConn(10, 2, AjConv.ObjID, Conv.ObjID)
#
#     # 网络连接对象总表
#     AllModelConn = [[Line1], [Line2], [Line3], [Line4],
#                     [Line5], [Line6], [Line7], [Line8],
#                     [Line9], [Line10]]
#     print(AllModelConn)
# X16.1赋值

AllModelObj = [
    ['DataSet1', 1, '数据集1', 'LoadData',
        'SetDataPara', [], 120, 330],
    ['Conv1', 2, '卷积1', 'ConvProc',
        'SetConvPara', [], 250, 330],
    ['Pool1', 3, '最大池化1', 'MaxPoolProc',
        'SetPollPara', [], 380, 330],
    ['FullConn1',4,'全连接1','FullConnProc',
        'SetFullConnPara', [], 510,330],
    ['Nonline1',5,'非线性函数1', 'NonlinearProc',
        'SetNonLPara', [], 640, 330],
    ['Classifier1',6,'分类1','ClassifierProc',
         'SetClassifyPara',[],780,330],
    ['Error1', 7, '误差计算1', 'ErrorProc',
      'SetErrorPara', [], 710, 124],
    ['AjConv1', 8, '卷积调整1', 'AjConvProc',
         'SetAjConvPara', [], 250, 70],
    ['AjFullconn1',9,'全连接调整1','AjFullconnProc',
         'SetAjFCPara',[],510,120]]
AllModelConn = [
[1, 1, 'DataSet1', 'Conv1'], [2, 1, 'Conv1', 'Pool1'],
[3, 1, 'Pool1', 'FullConn1'], [4, 1, 'FullConn1', 'Nonline1'],
[5, 1, 'Nonline1', 'Classifier1'], [6, 1, 'Classifier1', 'Error1'],
[7, 2, 'Error1', 'AjFullconn1'], [8, 2, 'Error1', 'AjConv1'],
[9, 2, 'AjFullconn1', 'FullConn1'], [10, 2, 'AjConv1', 'Conv1']]

# 定义一个函数，根据给定的参数生成一个AllModelObj对象
def create_almodelobj():
  all_model = []
  obj_label = ["数据集", "卷积", "池化", "全连接", "非线性",
              "分类", "误差计算", "卷积调整", "全连接调整"]
  proc_func = ['LoadData', 'ConvProc', 'MaxPoolProc', 'FullConnProc',
              'NonlinearProc', 'ClassifierProc', 'ErrorProc',
              'AjConvProc', 'AjFullconnProc']
  set_para_func = ['SetDataPara', 'SetConvPara', 'SetPollPara',
                 'SetFullConnPara', 'SetNonLPara', 'SetClassifyPara',
                 'SetErrorPara','SetAjConvPara', 'SetAjFCPara']
  for i in range(9):
    obj = []  # 创建一个列表，存储对象的属性
    obj.append(i)  # 对象ID
    obj.append(i % 9 + 1)  # 对象类型
    obj.append(obj_label[i % 9] + f"{i // 9 + 1}")         # 对象标签
    obj.append(proc_func[i % 9] + f"{i // 9 + 1}")         # 对象基本功能函数
    obj.append(set_para_func[i % 9] + f"{i // 9 + 1}")     # 对象功能函数参数
    obj.append('.')
    obj.append(i * 20)
    obj.append(i * 20)
    all_model.append(obj)
  return all_model


# if __name__ == '__main__':
#     all_model  = create_almodelobj()
#     print(all_model)

# 定义一个函数，生成一个AllModelConn对象的列表
def create_allmodelconn():
  all_conn = []  # 创建一个空列表，存储所有对象
  conn_obj_id = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] #储连接线编号
  conn_type = [1, 1, 1, 1, 1, 1, 2, 2, 2, 2]    #储连接线类别
  # 定义一个列表，存储源图元对象
  nobj_s = ["DataSet1", "Conv1", "Pool1", "FullConn1", "Nonline1",
            "Classifier1", "Error1", "Error1", "AjFullconn1", "AjConv1"]
  # 定义一个列表，存储目标图元对象
  nobj_e = ["Conv1", "Pool1", "FullConn1", "Nonline1", "Classifier1",
            "Error1", "AjFullconn1", "AjConv1", "FullConn1", "Conv1"]
  all_conn = [        # 使用列表推导式，根据给定的参数生成所有对象
    [                 # 创建一个列表，存储对象的属性
      conn_obj_id[i], # 连接线编号
      conn_type[i],   # 连接线类别
      nobj_s[i],      # 源图元对象
      nobj_e[i]       # 目标图元对象
    ]
    for i in range(len(conn_obj_id)) # 循环遍历连接线编号的长度
  ]
  return all_conn
# print(create_allmodelconn())

# if __name__ == '__main__':
#     all_conn  = create_allmodelconn()
#     print(all_conn)
# 导入需要的库
import numpy as np # 用于处理数组
from PIL import Image # 用于读取图片
'''
【编程16.2】编制程序：练习加载输入数据。
【目的及编程说明】读者通过编程16.2可理解加载数据的过程。本项目中假定输入数据的类型为图片数据，
要求读者能够实现将程序指定的图片转换为卷积神经网络模型能够接受的数据格式即数组。具体实现要求：
（1）程序读取一个固定路径的图片,；
（2）将图片缩放为 32x32固定大小，并将其转换为数组格式；
（3）将数组中具体数值归一化。该程序为编制LoadData()奠定基础。
'''
# 定义一个函数，实现图片转换为数组的功能
def image_to_array(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

# if __name__ == '__main__':
#     path = 'data/train/狗/1.jpg'   # 路径
#     height, width =32, 32       # 尺寸：h，w
#     data_image = image_to_array(path,height,width)
#     print(data_image)

'''
【编程16.3】编制程序：完成指定卷积核参数的卷积操作。
【目的及编程说明】读者通过编程16.3可理解卷积操作的具体计算过程。该函数的输入数据为数组格式的数据。具体实现要求：
（1）程序中给出卷积核大小为3*3的矩阵，并给矩阵赋初始值。例如初值[[ 1, -1, 2], [ 3, -2, 1], [ 0, 1, 0]]
或[[ 0, 1, 0], [ 1, -4, 1], [ 0, 1, 0]]或自己设定；
（2）默认步长（stride）为1；（3）完成卷积计算；
（4）返回卷积后的数组。该程序为编制ConvProc()奠定基础。
'''
# 导入需要的库
import numpy as np # 用于处理数组
# 定义一个函数，实现卷积操作的功能
def convolve(data, kernel):
  # 获取数据和卷积核的形状
  data_height, data_width = data.shape             # 获取数据高宽
  kernel_height, kernel_width = kernel.shape       # 获取卷积核高宽
  output_height = data_height - kernel_height + 1  # 以步长为1来计算输出的高宽
  output_width = data_width - kernel_width + 1
  output = np.zeros((output_height, output_width)) # 创建一个空的输出数组
  for i in range(output_height):  # 遍历输出数组的每个元素，计算卷积值
    for j in range(output_width):
      data_region = data[i:i+kernel_height, j:j+kernel_width] # 获取数据和卷积核的对应区域
      conv_value = np.sum(data_region * kernel) # 计算对应区域的元素乘积之和，作为卷积值
      output[i, j] = conv_value   # 将卷积值赋给输出数组的对应元素
  return output

# if __name__ == '__main__':
#     path = 'data/train/狗/1.jpg'  # 路径
#     height, width = 28, 28  # 尺寸：h，w
#     data_image = image_to_array(path, height, width)
#
#     # 定义一个卷积核，可以自己修改
#     kernel = np.array([[1, -1, 2],
#                      [3, -2, 1],
#                      [0, 1, 0]])
#     output = convolve(data_image, kernel) # 传入数据和卷积核
#     print(output)


'''
【编程16.4】编制程序：完成指定参数的全连接操作。
【目的及编程说明】读者通过编程16.4可理解全连接操作的具体计算过程。
该函数的输入数据为数组格式的数据，输出为计算后的数组。具体实现要求：
（1）程序中给定权重矩阵[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4]]和偏置向量[0.1, 0.2]
（注：可自己自主设定参数）；
（2）计算输入数据和权重矩阵的乘积（注意矩阵的维度），并加上偏置向量；
（3）返回计算后的数组。该程序为编制FullConnProc()奠定基础。
'''

# 定义一个函数，实现全连接操作
def full_connect(input_data, weight_matrix, bias_vector):
    output_data = np.dot(input_data, weight_matrix)   # 计算输入数据和权重矩阵的乘积
    bias_vector = np.expand_dims(bias_vector, axis=0) # 扩展偏置向量的形状
    bias_vector = np.expand_dims(bias_vector, axis=0)
    output_data = output_data + bias_vector           # 加上偏置向量
    return output_data # 返回输出数据

if __name__ == '__main__':
    weight_matrix = np.array([[0.1, 0.2, 0.3], [0.2, 0.3, 0.4]]) # 给定权重矩阵
    bias_vector = np.array([0.1, 0.2, 0.3]) # 给定偏置向量
    # 假设输入数据为一个三维数组，每一层代表一个样本，每一行代表一个特征，每一列代表一个通道
    input_data = np.array([[[1, 2], [3, 4], [5, 6]],
                            [[7, 8], [9, 10], [11, 12]],
                            [[13, 14], [15, 16], [17, 18]]])
    output_data = full_connect(input_data, weight_matrix, bias_vector)
    print(output_data)