import torch  
from net import FullyConnectedNet  
from PIL import Image  
import numpy as np  
from torchvision import transforms  
import torch.nn.functional as F 


device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

model = FullyConnectedNet()  #模型结构，参数是随机初始化的，和训练好的模型权重没有关系
model_path = "./mlp_mse/model/mnist_net_best.pth"  

# 加载训练好的权重到模型中：
# 1. torch.load(model_path, map_location=device) 从磁盘读取权重字典（state_dict），
#    map_location=device 保证权重被映射到当前使用的设备上（防止在 GPU 上训练的模型在纯 CPU 机器上加载失败）
# 2. model.load_state_dict(...) 把读取到的权重参数逐个复制进模型的对应层中
model.load_state_dict(torch.load(model_path, map_location=device))

model.to(device)  # 把模型的所有参数和缓冲区移动到指定设备上（GPU 或 CPU），之后前向计算就在该设备上进行
model.eval()  
# 读取图片并进行预处理
image_path = "D:/code/xwdev/mlp_mse/mnist_pytorch_samples/image_0003_label_0.png"  
image = Image.open(image_path)  # 用 Pillow 打开图片文件，得到一个 PIL Image 对象

# 定义图像预处理流水线，transforms.Compose 会把多个变换按顺序串联起来依次执行
transform = transforms.Compose([
        transforms.Grayscale(num_output_channels=1),  # 转为灰度图：无论原图是 RGB 还是 RGBA，都强制变成单通道灰度图（MNIST 是灰度图，通道数必须为 1）
        transforms.Resize((28, 28)),                  # 调整大小为 28x28：MNIST 模型训练时输入就是 28x28，推理时尺寸必须一致
        transforms.ToTensor(),                        # 转为 tensor：把 PIL 图片转成 PyTorch 张量，形状为 [C, H, W]（这里是 [1, 28, 28]），
                                                      # 同时把像素值从 0~255 的整数缩放到 0.0~1.0 的浮点数
    ])

input_tensor = transform(image)  # 对图片执行上面定义的预处理流水线，得到形状为 [1, 28, 28] 的张量（通道、高、宽）
input_tensor = input_tensor.unsqueeze(0)  # 添加 batch 维度：在第 0 维插入一个大小为 1 的维度，
                                          # 形状从 [1, 28, 28] 变为 [1, 1, 28, 28]，即 NCHW 格式（批量数、通道、高、宽），
                                          # 因为模型要求输入是一“批”图片，即使只有一张也要包成 batch
input_tensor = input_tensor.to(device)  # 把输入张量移动到与模型相同的设备上，设备和模型必须一致，否则计算时会报错

# 使用模型进行预测
with torch.no_grad():  # 上下文管理器：在其内部所有操作都不会记录梯度信息。
                       # 推理阶段不需要反向传播，关闭梯度可以节省内存、加快计算
    output = model(input_tensor)  # 前向传播：把图片张量送入模型，得到输出。
                                  # 由于网络最后一层是 Softmax(dim=1)，output 是形状 [1, 10] 的概率分布，
                                  # 每一维表示该图片属于数字 0~9 的概率，10 个概率之和为 1

    predicted_class = torch.argmax(output, dim=1).item()  # 取概率最大的那一维的索引作为预测类别：
                                                          # argmax(output, dim=1) 在第 1 维（类别维）上找最大值的下标，返回形状 [1] 的张量
                                                          # .item() 把只含一个元素的张量转成普通的 Python 整数
    print(f"Predicted class: {predicted_class}")  # 打印预测结果，例如 "Predicted class: 0"

    # 下面三行是把送入模型的图片显示出来，方便人工核对预处理结果
    img_display = input_tensor.squeeze().cpu().numpy()  # 把张量变回 NumPy 数组以便转成图片：
                                                        # squeeze() 去掉所有大小为 1 的维度，[1,1,28,28] -> [28,28]
                                                        # .cpu() 把张量从 GPU 移回 CPU（NumPy 只能处理 CPU 上的数据）
                                                        # .numpy() 把 PyTorch 张量转成 NumPy 数组，此时像素值仍是 0.0~1.0 的浮点数
    image = Image.fromarray((img_display * 255).astype(np.uint8))  # 把 NumPy 数组转回 PIL 图片：
                                                                   # *255 把像素值从 0~1 还原回 0~255
                                                                   # astype(np.uint8) 转成 8 位无符号整数（图片标准格式）
                                                                   # Image.fromarray 根据数组创建灰度图对象
    image.show()  # 调用系统默认的图片查看器显示这张图片
