import os
import argparse
import torch

import numpy as np
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import torchvision.transforms as transforms

from tqdm import trange, tqdm
from datetime import datetime

import utils
from utils import record_training_results, params_preview, FocalLoss
from torch.optim.lr_scheduler import ExponentialLR
# from medmnist.models import ResNet18, ResNet50
from medmnist.new_models import resnet18, resnet50
from medmnist.dataset import PathMNIST, ChestMNIST, DermaMNIST, OCTMNIST, PneumoniaMNIST, RetinaMNIST, \
    BreastMNIST, OrganMNISTAxial, OrganMNISTCoronal, OrganMNISTSagittal
from medmnist.evaluator import getAUC, getACC, save_results
from medmnist.info import INFO


def main(flag, input_root, output_root, end_epoch, download, aug, auto_lr, w_init, model_name, loss, record):
    """
    The main function.
    :param
        flag:
            name of MedMNIST subset.
        input_root:
            The input root.
        output_root:
            The output root.
        end_epoch:
            The maximum number of training epoches.
        download:
            Using downloaded file if True.
        aug:
            Executing more various data augmentation if True.
        auto_lr:
            Adapting automatically adjusted learning rates if True.
        w_init:
            Adapting wiser weights initialization when instancing a model if True.
        model:
            The name of the model to be used.
        record:
            Saving the training data (losses, AUC's , etc.) if True.
    """

    args_dict = {
        "_advanced": True,
        "end_epoch": end_epoch,
        "model_name": model_name,
        "flag": flag,
        "aug": aug,
        "auto_lr": auto_lr,
        "w_init": w_init,
        "loss": loss,
    }

    flag_to_class = {
        "pathmnist": PathMNIST,
        "chestmnist": ChestMNIST,
        "dermamnist": DermaMNIST,
        "octmnist": OCTMNIST,
        "pneumoniamnist": PneumoniaMNIST,
        "retinamnist": RetinaMNIST,
        "breastmnist": BreastMNIST,
        "organmnist_axial": OrganMNISTAxial,
        "organmnist_coronal": OrganMNISTCoronal,
        "organmnist_sagittal": OrganMNISTSagittal,
    }

    data_class = flag_to_class[flag]

    info = INFO[flag]
    task = info['task']
    n_channels = info['n_channels']
    n_classes = len(info['label'])

    start_epoch = 0
    batch_size = 128
    val_auc_list = []
    dir_path = os.path.join(output_root, "{}_checkpoints_epoch={}_model={}_aug={}_auto-lr={}_w-init={}_loss={}"
                            .format(flag, end_epoch, model_name, str(aug), str(auto_lr), str(w_init), loss))
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)

    print('==> Preparing data...')

    plain_transformation = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=[.5],
                             std=[.5])])

    # DONE: more augmentation methods

    augmented_transformation = transforms.Compose([
        transforms.ToTensor(),
        transforms.RandomHorizontalFlip(p=0.5),
        transforms.RandomVerticalFlip(p=0.5),
        transforms.RandomRotation(45),
        transforms.ColorJitter(brightness=0.1,
                               contrast=0.1,
                               saturation=0.1),
        transforms.Normalize(mean=[.5],
                             std=[.5])])

    if aug:
        transformation = augmented_transformation
    else:
        transformation = plain_transformation

    train_dataset = data_class(root=input_root,
                               split='train',
                               transform=transformation,
                               download=download)
    train_loader = data.DataLoader(dataset=train_dataset,
                                   batch_size=batch_size,
                                   shuffle=True)
    val_dataset = data_class(root=input_root,
                             split='val',
                             transform=transformation,
                             download=download)
    val_loader = data.DataLoader(dataset=val_dataset,
                                 batch_size=batch_size,
                                 shuffle=True)
    test_dataset = data_class(root=input_root,
                              split='test',
                              transform=transformation,
                              download=download)
    test_loader = data.DataLoader(dataset=test_dataset,
                                  batch_size=batch_size,
                                  shuffle=True)

    print('==> Building and training model...')

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # DONE: weights initialization
    # NOT DONE: ResNeSt

    if model_name == "res18":
        model = resnet18(in_channels=n_channels, num_classes=n_classes, w_init=w_init).to(device)
    elif model_name == "res50":
        model = resnet50(in_channels=n_channels, num_classes=n_classes, w_init=w_init).to(device)
    else:
        raise ValueError("[ERROR] invalid model name given. Use [--help] option for more info.")

    # DONE: different loss functions may work better

    criterion = None

    if loss == "ce" or loss == "f":
        if task == "multi-label, binary-class":
            criterion = nn.BCEWithLogitsLoss()
        elif task == "ordinal regression":
            criterion = nn.CrossEntropyLoss()

        if loss == "ce":
            criterion = nn.CrossEntropyLoss()
        elif loss == "f":
            criterion = utils.FocalLoss(class_num=n_classes)

    else:
        raise ValueError("[ERROR] invalid loss function given. Use [--help] option for more info.")

    lr = 0.001

    # DONE: LR scheduler

    optimizer = optim.SGD(model.parameters(),
                          lr=lr,
                          momentum=0.9)

    if record:
        record_training_results(content="{}\nTraining epoches: {}\n".
                                format(str(datetime.now().strftime("%Y%m%d %H%M%S")), end_epoch),
                                arguments_dict=args_dict)

    for epoch in trange(start_epoch, end_epoch):
        train(model, optimizer, criterion, train_loader, device, task, auto_lr=auto_lr)
        val(model, val_loader, device, val_auc_list, task, dir_path, epoch)

    auc_list = np.array(val_auc_list)
    index = auc_list.argmax()
    print('epoch %s is the best model' % index)

    if record:
        record_training_results(content="\tepoch {} is the best model\n"
                                .format(index),
                                arguments_dict=args_dict)

    print('==> Testing model...')
    restore_model_path = os.path.join(
        dir_path, 'ckpt_%d_auc_%.5f.pth' % (index, auc_list[index]))
    model.load_state_dict(torch.load(restore_model_path)['net'])

    dataset_labels = ["train", "val", "test"]
    loaders = [train_loader, val_loader, test_loader]

    for i in range(len(dataset_labels)):

        split, auc, acc, y_true, y_score = test(model,
                                                dataset_labels[i],
                                                loaders[i],
                                                device,
                                                task)

        if output_root is not None:
            output_dir = os.path.join(output_root, "{}_epoch={}_model={}_aug={}_auto-lr={}_w-init={}_loss={}"
                                      .format(flag, end_epoch, model_name, str(aug), str(auto_lr), str(w_init), loss))
            if not os.path.exists(output_dir):
                os.mkdir(output_dir)
            output_path = os.path.join(output_dir, "{}.csv".format(split))
            save_results(y_true, y_score, output_path)

        if record:
            record_training_results(content="\t{} AUC: {} ACC: {}\n"
                                    .format(split, auc, acc),
                                    arguments_dict=args_dict)


def train(model, optimizer, criterion, train_loader, device, task, auto_lr):
    """ training function
    :param model: the model to train
    :param optimizer: optimizer used in training
    :param criterion: loss function
    :param train_loader: DataLoader of training set
    :param device: cpu or cuda
    :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class
    :param auto_lr: uses lr scheduler if True
    """

    model.train()

    scheduler = None
    if auto_lr:
        scheduler = ExponentialLR(optimizer,
                                  gamma=0.9)

    for batch_idx, (inputs, targets) in enumerate(tqdm(train_loader)):
        optimizer.zero_grad()
        outputs = model(inputs.to(device))

        if task == 'multi-label, binary-class':
            targets = targets.to(torch.float32).to(device)
            loss = criterion(outputs, targets)
        else:
            targets = targets.squeeze().long().to(device)
            loss = criterion(outputs, targets)

        loss.backward()
        optimizer.step()

        if scheduler is not None:
            scheduler.step()


def val(model, val_loader, device, val_auc_list, task, dir_path, epoch):
    """ validation function
    :param model: the model to validate
    :param val_loader: DataLoader of validation set
    :param device: cpu or cuda
    :param val_auc_list: the list to save AUC score of each epoch
    :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class
    :param dir_path: where to save model
    :param epoch: current epoch

    """

    model.eval()
    y_true = torch.tensor([]).to(device)
    y_score = torch.tensor([]).to(device)
    with torch.no_grad():
        for batch_idx, (inputs, targets) in enumerate(val_loader):
            outputs = model(inputs.to(device))

            if task == 'multi-label, binary-class':
                targets = targets.to(torch.float32).to(device)
                m = nn.Sigmoid()
                outputs = m(outputs).to(device)
            else:
                targets = targets.squeeze().long().to(device)
                m = nn.Softmax(dim=1)
                outputs = m(outputs).to(device)
                targets = targets.float().resize_(len(targets), 1)

            y_true = torch.cat((y_true, targets), 0)
            y_score = torch.cat((y_score, outputs), 0)

        y_true = y_true.cpu().numpy()
        y_score = y_score.detach().cpu().numpy()
        auc = getAUC(y_true, y_score, task)
        val_auc_list.append(auc)

    state = {
        'net': model.state_dict(),
        'auc': auc,
        'epoch': epoch,
    }

    path = os.path.join(dir_path, 'ckpt_%d_auc_%.5f.pth' % (epoch, auc))
    torch.save(state, path)


def test(model, split, data_loader, device, task):
    """ testing function
    :param model: the model to test
    :param split: the data to test, 'train/val/test'
    :param data_loader: DataLoader of data
    :param device: cpu or cuda
    # :param flag: subset name
    :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class

    """

    model.eval()
    y_true = torch.tensor([]).to(device)
    y_score = torch.tensor([]).to(device)

    with torch.no_grad():
        for batch_idx, (inputs, targets) in enumerate(data_loader):
            outputs = model(inputs.to(device))

            if task == 'multi-label, binary-class':
                targets = targets.to(torch.float32).to(device)
                m = nn.Sigmoid()
                outputs = m(outputs).to(device)
            else:
                targets = targets.squeeze().long().to(device)
                m = nn.Softmax(dim=1)
                outputs = m(outputs).to(device)
                targets = targets.float().resize_(len(targets), 1)

            y_true = torch.cat((y_true, targets), 0)
            y_score = torch.cat((y_score, outputs), 0)

        y_true = y_true.cpu().numpy()
        y_score = y_score.detach().cpu().numpy()
        auc = getAUC(y_true, y_score, task)
        acc = getACC(y_true, y_score, task)
        print('%s AUC: %.5f ACC: %.5f' % (split, auc, acc))

    return split, auc, acc, y_true, y_score


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='RUN Advanced model of MedMNIST.')
    parser.add_argument('--data_name',
                        required=True,
                        help='subset of MedMNIST',
                        type=str)
    parser.add_argument('--input_root',
                        default='./input',
                        help='input root, the source of dataset files',
                        type=str)
    parser.add_argument('--output_root',
                        default='./output/advanced',
                        help='output root, where to save models and results',
                        type=str)
    parser.add_argument('--num_epoch',
                        default=100,
                        help='num of epochs of training',
                        type=int)
    parser.add_argument('--download',
                        default=True,
                        help='whether download the dataset or not',
                        type=lambda x: (str(x).lower() == 'true'))

    parser.add_argument("--aug",
                        required=True,
                        help="will execute more various data augmentation (like random cropping and flipping) if True",
                        type=lambda x: (str(x).lower() == 'true'))
    parser.add_argument("--auto_lr",
                        required=True,
                        help="will automatically adjust learning rate if True",
                        type=lambda x: (str(x).lower() == 'true'))
    parser.add_argument("--w_init",
                        required=True,
                        help="will execute Javier initialization of True",
                        type=lambda x: (str(x).lower() == 'true'))
    parser.add_argument("--model",
                        required=True,
                        help="res18: resnet-18\nres50: resnet-50\nresnest: resnest",
                        type=str)

    parser.add_argument("--loss",
                        required=True,
                        help="Chooses loss function. Available choices:\n"
                             "\t\"multi class\" and \"binary class\":\n"
                             "\t\tce (cross entropy loss)\n"
                             "\t\tf (focal loss, useful for imbalanced labels)\n"
                             "\tmulti-label, binary-class:\n"
                             "\t\t(uses BCEWithLogitsLoss as default)\n"
                             "\tordinal regression:\n"
                             "\t\t(uses CrossEntropyLoss as default)\n")

    parser.add_argument("--record",
                        default=False,
                        help="will record the training results (AUC, ACC...) if True",
                        type=lambda x: (str(x).lower() == 'true'))

    args = parser.parse_args()

    _dataset_name = args.data_name.lower()
    _input_root = args.input_root
    _output_root = args.output_root
    _num_epoch = args.num_epoch
    _download = args.download

    _aug = args.aug
    _auto_lr = args.auto_lr
    _w_init = args.w_init
    _model_name = args.model

    _loss = args.loss

    _record = args.record

    params_preview(args)

    main(_dataset_name,
         _input_root,
         _output_root,
         end_epoch=_num_epoch,
         download=_download,
         aug=_aug,
         auto_lr=_auto_lr,
         w_init=_w_init,
         model_name=_model_name,
         loss=_loss,
         record=_record)

import os
import argparse
import torch

import numpy as np
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import torchvision.transforms as transforms

from tqdm import trange, tqdm
from datetime import datetime

import utils
from utils import record_training_results, params_preview, FocalLoss
from torch.optim.lr_scheduler import ExponentialLR
# from medmnist.models import ResNet18, ResNet50
from medmnist.new_models import resnet18, resnet50
from medmnist.dataset import PathMNIST, ChestMNIST, DermaMNIST, OCTMNIST, PneumoniaMNIST, RetinaMNIST, \
    BreastMNIST, OrganMNISTAxial, OrganMNISTCoronal, OrganMNISTSagittal
from medmnist.evaluator import getAUC, getACC, save_results
from medmnist.info import INFO


def main(flag, input_root, output_root, end_epoch, download, aug, auto_lr, w_init, model_name, loss, record):
    """
    The main function.
    :param
        flag:
            name of MedMNIST subset.
        input_root:
            The input root.
        output_root:
            The output root.
        end_epoch:
            The maximum number of training epoches.
        download:
            Using downloaded file if True.
        aug:
            Executing more various data augmentation if True.
        auto_lr:
            Adapting automatically adjusted learning rates if True.
        w_init:
            Adapting wiser weights initialization when instancing a model if True.
        model:
            The name of the model to be used.
        record:
            Saving the training data (losses, AUC's , etc.) if True.
    """

    args_dict = {
        "_advanced": True,
        "end_epoch": end_epoch,
        "model_name": model_name,
        "flag": flag,
        "aug": aug,
        "auto_lr": auto_lr,
        "w_init": w_init,
        "loss": loss,
    }

    flag_to_class = {
        "pathmnist": PathMNIST,
        "chestmnist": ChestMNIST,
        "dermamnist": DermaMNIST,
        "octmnist": OCTMNIST,
        "pneumoniamnist": PneumoniaMNIST,
        "retinamnist": RetinaMNIST,
        "breastmnist": BreastMNIST,
        "organmnist_axial": OrganMNISTAxial,
        "organmnist_coronal": OrganMNISTCoronal,
        "organmnist_sagittal": OrganMNISTSagittal,
    }

    data_class = flag_to_class[flag]

    info = INFO[flag]
    task = info['task']
    n_channels = info['n_channels']
    n_classes = len(info['label'])

    start_epoch = 0
    batch_size = 128
    val_auc_list = []
    dir_path = os.path.join(output_root, "{}_checkpoints_epoch={}_model={}_aug={}_auto-lr={}_w-init={}_loss={}"
                            .format(flag, end_epoch, model_name, str(aug), str(auto_lr), str(w_init), loss))
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)

    print('==> Preparing data...')

    plain_transformation = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=[.5],
                             std=[.5])])

    # DONE: more augmentation methods

    augmented_transformation = transforms.Compose([
        transforms.ToTensor(),
        transforms.RandomHorizontalFlip(p=0.5),
        transforms.RandomVerticalFlip(p=0.5),
        transforms.RandomRotation(45),
        transforms.ColorJitter(brightness=0.1,
                               contrast=0.1,
                               saturation=0.1),
        transforms.Normalize(mean=[.5],
                             std=[.5])])

    if aug:
        transformation = augmented_transformation
    else:
        transformation = plain_transformation

    train_dataset = data_class(root=input_root,
                               split='train',
                               transform=transformation,
                               download=download)
    train_loader = data.DataLoader(dataset=train_dataset,
                                   batch_size=batch_size,
                                   shuffle=True)
    val_dataset = data_class(root=input_root,
                             split='val',
                             transform=transformation,
                             download=download)
    val_loader = data.DataLoader(dataset=val_dataset,
                                 batch_size=batch_size,
                                 shuffle=True)
    test_dataset = data_class(root=input_root,
                              split='test',
                              transform=transformation,
                              download=download)
    test_loader = data.DataLoader(dataset=test_dataset,
                                  batch_size=batch_size,
                                  shuffle=True)

    print('==> Building and training model...')

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # DONE: weights initialization
    # NOT DONE: ResNeSt

    if model_name == "res18":
        model = resnet18(in_channels=n_channels, num_classes=n_classes, w_init=w_init).to(device)
    elif model_name == "res50":
        model = resnet50(in_channels=n_channels, num_classes=n_classes, w_init=w_init).to(device)
    else:
        raise ValueError("[ERROR] invalid model name given. Use [--help] option for more info.")

    # DONE: different loss functions may work better

    criterion = None

    if loss == "ce" or loss == "f":
        if task == "multi-label, binary-class":
            criterion = nn.BCEWithLogitsLoss()
        elif task == "ordinal regression":
            criterion = nn.CrossEntropyLoss()

        if loss == "ce":
            criterion = nn.CrossEntropyLoss()
        elif loss == "f":
            criterion = utils.FocalLoss(class_num=n_classes)

    else:
        raise ValueError("[ERROR] invalid loss function given. Use [--help] option for more info.")

    lr = 0.001

    # DONE: LR scheduler

    optimizer = optim.SGD(model.parameters(),
                          lr=lr,
                          momentum=0.9)

    if record:
        record_training_results(content="{}\nTraining epoches: {}\n".
                                format(str(datetime.now().strftime("%Y%m%d %H%M%S")), end_epoch),
                                arguments_dict=args_dict)

    for epoch in trange(start_epoch, end_epoch):
        train(model, optimizer, criterion, train_loader, device, task, auto_lr=auto_lr)
        val(model, val_loader, device, val_auc_list, task, dir_path, epoch)

    auc_list = np.array(val_auc_list)
    index = auc_list.argmax()
    print('epoch %s is the best model' % index)

    if record:
        record_training_results(content="\tepoch {} is the best model\n"
                                .format(index),
                                arguments_dict=args_dict)

    print('==> Testing model...')
    restore_model_path = os.path.join(
        dir_path, 'ckpt_%d_auc_%.5f.pth' % (index, auc_list[index]))
    model.load_state_dict(torch.load(restore_model_path)['net'])

    dataset_labels = ["train", "val", "test"]
    loaders = [train_loader, val_loader, test_loader]

    for i in range(len(dataset_labels)):

        split, auc, acc, y_true, y_score = test(model,
                                                dataset_labels[i],
                                                loaders[i],
                                                device,
                                                task)

        if output_root is not None:
            output_dir = os.path.join(output_root, "{}_epoch={}_model={}_aug={}_auto-lr={}_w-init={}_loss={}"
                                      .format(flag, end_epoch, model_name, str(aug), str(auto_lr), str(w_init), loss))
            if not os.path.exists(output_dir):
                os.mkdir(output_dir)
            output_path = os.path.join(output_dir, "{}.csv".format(split))
            save_results(y_true, y_score, output_path)

        if record:
            record_training_results(content="\t{} AUC: {} ACC: {}\n"
                                    .format(split, auc, acc),
                                    arguments_dict=args_dict)


def train(model, optimizer, criterion, train_loader, device, task, auto_lr):
    """ training function
    :param model: the model to train
    :param optimizer: optimizer used in training
    :param criterion: loss function
    :param train_loader: DataLoader of training set
    :param device: cpu or cuda
    :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class
    :param auto_lr: uses lr scheduler if True
    """

    model.train()

    scheduler = None
    if auto_lr:
        scheduler = ExponentialLR(optimizer,
                                  gamma=0.9)

    for batch_idx, (inputs, targets) in enumerate(tqdm(train_loader)):
        optimizer.zero_grad()
        outputs = model(inputs.to(device))

        if task == 'multi-label, binary-class':
            targets = targets.to(torch.float32).to(device)
            loss = criterion(outputs, targets)
        else:
            targets = targets.squeeze().long().to(device)
            loss = criterion(outputs, targets)

        loss.backward()
        optimizer.step()

        if scheduler is not None:
            scheduler.step()


def val(model, val_loader, device, val_auc_list, task, dir_path, epoch):
    """ validation function
    :param model: the model to validate
    :param val_loader: DataLoader of validation set
    :param device: cpu or cuda
    :param val_auc_list: the list to save AUC score of each epoch
    :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class
    :param dir_path: where to save model
    :param epoch: current epoch

    """

    model.eval()
    y_true = torch.tensor([]).to(device)
    y_score = torch.tensor([]).to(device)
    with torch.no_grad():
        for batch_idx, (inputs, targets) in enumerate(val_loader):
            outputs = model(inputs.to(device))

            if task == 'multi-label, binary-class':
                targets = targets.to(torch.float32).to(device)
                m = nn.Sigmoid()
                outputs = m(outputs).to(device)
            else:
                targets = targets.squeeze().long().to(device)
                m = nn.Softmax(dim=1)
                outputs = m(outputs).to(device)
                targets = targets.float().resize_(len(targets), 1)

            y_true = torch.cat((y_true, targets), 0)
            y_score = torch.cat((y_score, outputs), 0)

        y_true = y_true.cpu().numpy()
        y_score = y_score.detach().cpu().numpy()
        auc = getAUC(y_true, y_score, task)
        val_auc_list.append(auc)

    state = {
        'net': model.state_dict(),
        'auc': auc,
        'epoch': epoch,
    }

    path = os.path.join(dir_path, 'ckpt_%d_auc_%.5f.pth' % (epoch, auc))
    torch.save(state, path)


def test(model, split, data_loader, device, task):
    """ testing function
    :param model: the model to test
    :param split: the data to test, 'train/val/test'
    :param data_loader: DataLoader of data
    :param device: cpu or cuda
    # :param flag: subset name
    :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class

    """

    model.eval()
    y_true = torch.tensor([]).to(device)
    y_score = torch.tensor([]).to(device)

    with torch.no_grad():
        for batch_idx, (inputs, targets) in enumerate(data_loader):
            outputs = model(inputs.to(device))

            if task == 'multi-label, binary-class':
                targets = targets.to(torch.float32).to(device)
                m = nn.Sigmoid()
                outputs = m(outputs).to(device)
            else:
                targets = targets.squeeze().long().to(device)
                m = nn.Softmax(dim=1)
                outputs = m(outputs).to(device)
                targets = targets.float().resize_(len(targets), 1)

            y_true = torch.cat((y_true, targets), 0)
            y_score = torch.cat((y_score, outputs), 0)

        y_true = y_true.cpu().numpy()
        y_score = y_score.detach().cpu().numpy()
        auc = getAUC(y_true, y_score, task)
        acc = getACC(y_true, y_score, task)
        print('%s AUC: %.5f ACC: %.5f' % (split, auc, acc))

    return split, auc, acc, y_true, y_score


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='RUN Advanced model of MedMNIST.')
    parser.add_argument('--data_name',
                        required=True,
                        help='subset of MedMNIST',
                        type=str)
    parser.add_argument('--input_root',
                        default='./input',
                        help='input root, the source of dataset files',
                        type=str)
    parser.add_argument('--output_root',
                        default='./output/advanced',
                        help='output root, where to save models and results',
                        type=str)
    parser.add_argument('--num_epoch',
                        default=100,
                        help='num of epochs of training',
                        type=int)
    parser.add_argument('--download',
                        default=True,
                        help='whether download the dataset or not',
                        type=lambda x: (str(x).lower() == 'true'))

    parser.add_argument("--aug",
                        required=True,
                        help="will execute more various data augmentation (like random cropping and flipping) if True",
                        type=lambda x: (str(x).lower() == 'true'))
    parser.add_argument("--auto_lr",
                        required=True,
                        help="will automatically adjust learning rate if True",
                        type=lambda x: (str(x).lower() == 'true'))
    parser.add_argument("--w_init",
                        required=True,
                        help="will execute Javier initialization of True",
                        type=lambda x: (str(x).lower() == 'true'))
    parser.add_argument("--model",
                        required=True,
                        help="res18: resnet-18\nres50: resnet-50\nresnest: resnest",
                        type=str)

    parser.add_argument("--loss",
                        required=True,
                        help="Chooses loss function. Available choices:\n"
                             "\t\"multi class\" and \"binary class\":\n"
                             "\t\tce (cross entropy loss)\n"
                             "\t\tf (focal loss, useful for imbalanced labels)\n"
                             "\tmulti-label, binary-class:\n"
                             "\t\t(uses BCEWithLogitsLoss as default)\n"
                             "\tordinal regression:\n"
                             "\t\t(uses CrossEntropyLoss as default)\n")

    parser.add_argument("--record",
                        default=False,
                        help="will record the training results (AUC, ACC...) if True",
                        type=lambda x: (str(x).lower() == 'true'))

    args = parser.parse_args()

    _dataset_name = args.data_name.lower()
    _input_root = args.input_root
    _output_root = args.output_root
    _num_epoch = args.num_epoch
    _download = args.download

    _aug = args.aug
    _auto_lr = args.auto_lr
    _w_init = args.w_init
    _model_name = args.model

    _loss = args.loss

    _record = args.record

    params_preview(args)

    main(_dataset_name,
         _input_root,
         _output_root,
         end_epoch=_num_epoch,
         download=_download,
         aug=_aug,
         auto_lr=_auto_lr,
         w_init=_w_init,
         model_name=_model_name,
         loss=_loss,
         record=_record)

# from medmnist.models import ResNet18, ResNet50 The main function.     :param         flag:             name of MedMNIST subset.         input_root:             The input root.         output_root:             The output root.         end_epoch:             The maximum number of training epoches.         download:             Using downloaded file if True.         aug:             Executing more various data augmentation if True.         auto_lr:             Adapting automatically adjusted learning rates if True.         w_init:             Adapting wiser weights initialization when instancing a model if True.         model:             The name of the model to be used.         record:             Saving the training data (losses, AUC's , etc.) if True. # DONE: more augmentation methods # DONE: weights initialization # NOT DONE: ResNeSt # DONE: different loss functions may work better # DONE: LR scheduler training function     :param model: the model to train     :param optimizer: optimizer used in training     :param criterion: loss function     :param train_loader: DataLoader of training set     :param device: cpu or cuda     :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class     :param auto_lr: uses lr scheduler if True validation function     :param model: the model to validate     :param val_loader: DataLoader of validation set     :param device: cpu or cuda     :param val_auc_list: the list to save AUC score of each epoch     :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class     :param dir_path: where to save model     :param epoch: current epoch testing function     :param model: the model to test     :param split: the data to test, 'train/val/test'     :param data_loader: DataLoader of data     :param device: cpu or cuda     # :param flag: subset name     :param task: task of current dataset, binary-class/multi-class/multi-label, binary-class