{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "import gc\n",
    "import os\n",
    "import cv2\n",
    "import math\n",
    "import base64\n",
    "import random\n",
    "import numpy as np\n",
    "from PIL import Image \n",
    "from tqdm import tqdm\n",
    "from datetime import datetime\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from torch.cuda import amp\n",
    "import torch.nn.functional as F\n",
    "from torch.optim import Adam, AdamW\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "\n",
    "import torchvision\n",
    "import torchvision.transforms as TF\n",
    "import torchvision.datasets as datasets\n",
    "from torchvision.utils import make_grid\n",
    "\n",
    "from torchmetrics import MeanMetric\n",
    "\n",
    "from IPython.display import display, HTML, clear_output\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Helper functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "def to_device(data, device):\n",
    "    \"\"\"将张量移动到选择的设备\"\"\"\n",
    "    \"\"\"Move tensor(s) to chosen device\"\"\"\n",
    "    if isinstance(data, (list, tuple)):\n",
    "        return [to_device(x, device) for x in data]\n",
    "    return data.to(device, non_blocking=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DeviceDataLoader:\n",
    "    \"\"\"包装一个数据加载器，来把数据移动到另一个设备上\"\"\"\n",
    "    \"\"\"Wrap a dataloader to move data to a device\"\"\"\n",
    "\n",
    "    def __init__(self, dl, device):\n",
    "        self.dl = dl\n",
    "        self.device = device\n",
    "\n",
    "    def __iter__(self):\n",
    "        \"\"\"在移动到设备后生成一个批次的数据\"\"\"\n",
    "        \"\"\"Yield a batch of data after moving it to device\"\"\"\n",
    "        for b in self.dl:\n",
    "            yield to_device(b, self.device)\n",
    "\n",
    "    def __len__(self):\n",
    "        \"\"\"批次的数量\"\"\"\n",
    "        \"\"\"Number of batches\"\"\"\n",
    "        return len(self.dl)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_default_device():\n",
    "    return torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "def save_images(images, path, **kwargs):\n",
    "    grid = make_grid(images, **kwargs)\n",
    "    ndarr = grid.permute(1,2,0).to(\"cpu\").numpy()\n",
    "    im = Image.fromarray(ndarr)\n",
    "    im.save(path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get(element: torch.Tensor, t: torch.Tensor):\n",
    "    \"\"\"\n",
    "    Get value at index position \"t\" in \"element\" and \n",
    "        reshape it to have the same dimension as a batch of images\n",
    "\n",
    "    获得在\"element\"中位置\"t\"并且reshape，以和一组照片有相同的维度\n",
    "    \"\"\"\n",
    "    ele = element.gather(-1, t)\n",
    "    return ele.reshape(-1, 1, 1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "element = torch.tensor([[1,2,3,4,5],\n",
    "                        [2,3,4,5,6],\n",
    "                        [3,4,5,6,7]])\n",
    "t = torch.tensor([1,2,0]).unsqueeze(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1, 2, 3, 4, 5],\n",
      "        [2, 3, 4, 5, 6],\n",
      "        [3, 4, 5, 6, 7]])\n",
      "tensor([[1],\n",
      "        [2],\n",
      "        [0]])\n"
     ]
    }
   ],
   "source": [
    "print(element)\n",
    "print(t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[[[2]]],\n",
      "\n",
      "\n",
      "        [[[4]]],\n",
      "\n",
      "\n",
      "        [[[3]]]])\n"
     ]
    }
   ],
   "source": [
    "extracted_scores = get(element, t)\n",
    "print(extracted_scores)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "def setup_log_directory(config):\n",
    "    \"\"\"\n",
    "    Log and Model checkpoint directory Setup\n",
    "    记录并且建模目录准备\n",
    "    \"\"\"\n",
    "\n",
    "    if os.path.isdir(config.root_log_dir):\n",
    "        # Get all folders numbers in the root_log_dir\n",
    "        # 在root_log_dir下获得所有文件夹数目\n",
    "        folder_numbers = [int(folder.replace(\"version_\", \"\")) for folder in os.listdir(config.root_log_dir)]\n",
    "\n",
    "        # Find the latest version number present in the log_dir\n",
    "        # 找到在log_dir下的最新版本数字\n",
    "        last_version_number = max(folder_numbers)\n",
    "\n",
    "        # New version name\n",
    "        version_name = f\"version{last_version_number + 1}\"\n",
    "\n",
    "    else:\n",
    "        version_name = config.log_dir\n",
    "\n",
    "    # Update the training config default directory\n",
    "    # 更新训练config默认目录\n",
    "    log_dir         = os.path.join(config.root_log_dir,         version_name)\n",
    "    checkpoint_dir  = os.path.join(config.root_checkpoint_dir,  version_name) \n",
    "\n",
    "    # Create new directory for saving new experiment version\n",
    "    # 创建一个新目录来保存新的实验版本\n",
    "    os.makedirs(log_dir,        exist_ok=True)\n",
    "    os.makedirs(checkpoint_dir, exist_ok=True)\n",
    "\n",
    "    print(f\"Logging at: {log_dir}\")\n",
    "    print(f\"Model Checkpoint at: {checkpoint_dir}\")\n",
    "\n",
    "    return log_dir, checkpoint_dir\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "def frames2vid(images, save_path):\n",
    "\n",
    "    WIDTH = images[0].shape[1]\n",
    "    HEIGHT = images[0].shape[0]\n",
    "\n",
    "    # fourcc = cv2.VideoWriter_fourcc(*'XVID')\n",
    "    fourcc = cv2.VideoWriter_fourcc(*'mp4v')\n",
    "    video = cv2.VideoWriter(save_path, fourcc, 25, (WIDTH, HEIGHT))\n",
    "\n",
    "    # Appending the images to the video one by one\n",
    "    # 一个接一个的将照片追加到视频\n",
    "    for image in images:\n",
    "        video.write(image)\n",
    "    \n",
    "    # Deallocating memories taken for window creation\n",
    "    # 释放创建window占用的内存\n",
    "        \n",
    "    video.release()\n",
    "    return\n",
    "\n",
    "def display_gif(gif_path):\n",
    "    b64 = base64.b64encode(open(gif_path,'rb').read()).decode('ascii')\n",
    "    display(HTML(f'<img src=\"data:image/gif;base64,{b64}\" />'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configurations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "from dataclasses import dataclass\n",
    "\n",
    "@dataclass\n",
    "class BaseConfig:\n",
    "    DEVICE = get_default_device()\n",
    "    DATASET = \"Flowers\" #MNIST \"cifar-10\" \"Flowers\"\n",
    "\n",
    "    # 记录推断日志信息并保存存档点\n",
    "    root_log_dir = os.path.join(\"Logs_Checkpoints\", \"Inference\")\n",
    "    root_checkpoint_dir = os.path.join(\"Logs_Checkpoints\",\"checkpoints\")\n",
    "\n",
    "    #目前的日志和存档点目录\n",
    "    log_dir = \"version_0\"\n",
    "    checkpoint_dir = \"version_0\"\n",
    "\n",
    "@dataclass\n",
    "class TrainingConfig:\n",
    "    TIMESTEPS = 1000\n",
    "    IMG_SHAPE = (1,32,32) if BaseConfig.DATASET == \"MNIST\" else (3,32,32)\n",
    "    NUM_EPOCHS = 800\n",
    "    BATCH_SIZE = 32\n",
    "    LR = 2e-4\n",
    "    NUM_WORKERS = 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load Dataset & Build Dataloader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_dataset(dataset_name='MNIST'):\n",
    "    \"\"\"\n",
    "    Returns the dataset class object that will be passed to the Dataloader\n",
    "    Three preprocessing transforms,\n",
    "    and one augmentation are applied to every image in the dataset\n",
    "    返回数据集的类对象\n",
    "    这个类对象将会被传递给DataLoader\n",
    "    数据集中的每个图象将会应用三个预处理转换\n",
    "    和一个增强\n",
    "    \"\"\"\n",
    "    transforms = TF.Compose(\n",
    "        [\n",
    "            TF.ToTensor(),\n",
    "            TF.Resize((32,32),\n",
    "                      interpolation=TF.InterpolationMode.BICUBIC,\n",
    "                      antialias=True),\n",
    "            TF.RandomHorizontalFlip(),\n",
    "            TF.Lambda(lambda t: (t * 2) - 1) # scale between [-1, 1]\n",
    "        ]\n",
    "    )\n",
    "\n",
    "    if dataset_name.upper() == \"MNIST\":\n",
    "        dataset = datasets.MNIST(root=\"data\", train=True, download=True, transform=transforms)\n",
    "    elif dataset_name == \"Cifar-10\":\n",
    "        dataset = datasets.CIFAR10(root=\"data\", train=True,download=True, transform=transforms)\n",
    "    elif dataset_name == \"Cifar-100\":\n",
    "        dataset = datasets.CIFAR10(root=\"data\", train=True,download=True, transform=transforms)\n",
    "    elif dataset_name == \"Flowers\":\n",
    "        dataset = datasets.ImageFolder(root=\"data/flowers\", transform=transforms)\n",
    "    return dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_dataloader(dataset_name='MNIST',\n",
    "                   batch_size=32,\n",
    "                   pin_memory=False,\n",
    "                   shuffle=True,\n",
    "                   num_workers=0,\n",
    "                   device=\"cpu\"\n",
    "                   ):\n",
    "    dataset     = get_dataset(dataset_name=dataset_name)\n",
    "    dataLoader  = DataLoader(dataset, batch_size=batch_size,\n",
    "                             pin_memory=pin_memory,\n",
    "                             num_workers=num_workers,\n",
    "                             shuffle=shuffle\n",
    "                             )\n",
    "    device_dataloader = DeviceDataLoader(dataLoader, device)\n",
    "    return device_dataloader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "def inverse_transform(tensors):\n",
    "    \"\"\"\n",
    "    Convert tensors from [-1., 1.] to [0., 255.]\n",
    "    \"\"\"\n",
    "    return ((tensors.clamp(-1, 1) + 1.0) / 2.0) * 255.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dataset ImageFolder\n",
      "    Number of datapoints: 4317\n",
      "    Root location: data/flowers\n",
      "    StandardTransform\n",
      "Transform: Compose(\n",
      "               ToTensor()\n",
      "               Resize(size=(32, 32), interpolation=bicubic, max_size=None, antialias=True)\n",
      "               RandomHorizontalFlip(p=0.5)\n",
      "               Lambda()\n",
      "           )\n"
     ]
    }
   ],
   "source": [
    "dataset = get_dataset(dataset_name='Flowers')\n",
    "print(dataset)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualize Dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "loader = get_dataloader(\n",
    "    dataset_name=BaseConfig.DATASET,\n",
    "    batch_size=128,\n",
    "    device='cpu'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1133268 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(12, 6), facecolor='white')\n",
    "\n",
    "for b_image, _ in loader:\n",
    "    b_image = inverse_transform(b_image).cpu()\n",
    "    grid_img = make_grid(b_image / 255.0, \n",
    "                         nrow = 16, \n",
    "                         padding=True,\n",
    "                         pad_value=1,\n",
    "                         normalize=True \n",
    "                        )\n",
    "    plt.imshow(grid_img.permute(1, 2, 0))\n",
    "    plt.axis(\"off\")\n",
    "    break"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "DLML",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
