import torch
import numpy as np
from lucent.optvis.param.spatial import rfft2d_freqs
from lucent.optvis.param.color import to_valid_rgb
from collections import OrderedDict, defaultdict
import os

import os.path as path
from itertools import chain
from reportlab.platypus import SimpleDocTemplate, Image, Paragraph, Spacer
from reportlab.lib.pagesizes import A4, letter
from PIL import Image as PImage
from reportlab.lib.styles import ParagraphStyle
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader, Dataset

import matplotlib.pyplot as plt
import pandas as pd
# TODO: Remove the stuff for results generation from ehre and put it into its own utils file


def get_train_dataloader(batch_size=256, num_workers=10, imagenet_dir="/mnt/disk/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_train_transform = transforms.Compose(
        [
            # transforms.Resize(256),
            transforms.RandomResizedCrop(224),
            transforms.RandomHorizontalFlip(),
            transforms.ToTensor(),
            normalize,
        ]
    )
    train_dataset = datasets.ImageNet(imagenet_dir, split="train", transform=standard_train_transform)
    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=batch_size, num_workers=num_workers, pin_memory=True, shuffle=True
    )
    return train_loader

def get_train_dataloader_Subset(nb_images = 10, batch_size = 256, num_workers = 10, 
                                imagenet_dir = "/mnt/disk/imagenet", N_classes = 1000, seed = 111):
    np.random.seed(seed)
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_train_transform = transforms.Compose(
        [
            # transforms.Resize(256),
            transforms.Resize(256), 
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            normalize,
        ]
    )

    train_dataset = datasets.ImageNet(imagenet_dir, split="train", transform = standard_train_transform)
    sample_indexes = np.array([], dtype=np.int64)

    index_per_class_dict = defaultdict(list)
    for i, (img_path, label) in enumerate(train_dataset.imgs):
        index_per_class_dict[label].append(i)

    for label in range(N_classes):
        ind_ref_class = np.random.choice(index_per_class_dict[label], nb_images, replace=False,)
        sample_indexes = np.concatenate((sample_indexes, ind_ref_class), axis = 0)

    my_subset = torch.utils.data.Subset(train_dataset, sample_indexes)
    
    train_loader = DataLoader(
        my_subset, batch_size=batch_size, num_workers=num_workers, pin_memory=True, shuffle=False
    )
    return train_loader


def get_train_dataloader_Subset_with_inds_eval(nb_images = 10, batch_size = 256, num_workers = 10, 
                                imagenet_dir = "/mnt/disk/imagenet", N_classes = 1000, seed = 111):
    np.random.seed(seed)
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_train_transform = transforms.Compose(
        [
            # transforms.Resize(256),
            transforms.Resize(256), 
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            normalize,
        ]
    )

    train_dataset = ImageNetWithIndices(imagenet_dir, split="train", transform = standard_train_transform)
    sample_indexes = np.array([], dtype=np.int64)

    index_per_class_dict = defaultdict(list)
    for i, (img_path, label) in enumerate(train_dataset.imgs):
        index_per_class_dict[label].append(i)

    for label in range(N_classes):
        ind_ref_class = np.random.choice(index_per_class_dict[label], nb_images, replace=False,)
        sample_indexes = np.concatenate((sample_indexes, ind_ref_class), axis = 0)

    my_subset = torch.utils.data.Subset(train_dataset, sample_indexes)
    
    train_loader = DataLoader(
        my_subset, batch_size=batch_size, num_workers=num_workers, pin_memory=True, shuffle=False
    )
    return train_loader

def get_train_dataloader_for_eval(batch_size=256, num_workers=10, imagenet_dir="/mnt/disk/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_train_transform = transforms.Compose(
        [   # transforms.Resize(256),
            transforms.Resize(256), 
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            normalize,
        ]
    )
    train_dataset = datasets.ImageNet(imagenet_dir, split="train", transform=standard_train_transform)
    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=batch_size, num_workers=num_workers, pin_memory=True, shuffle=True
    )
    return train_loader

def get_train_dataloader_for_eval_with_inds(batch_size=256, num_workers=10, imagenet_dir="/mnt/disk/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_train_transform = transforms.Compose(
        [   # transforms.Resize(256),
            transforms.Resize(256), 
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            normalize,
        ]
    )
    train_dataset = ImageNetWithIndices(imagenet_dir, split="train", transform=standard_train_transform)
    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=batch_size, num_workers=num_workers, pin_memory=True, shuffle=True
    )
    return train_loader

def get_test_dataloader_with_inds(batch_size=256, num_workers=10, imagenet_dir="/mnt/disk/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_test_transform = transforms.Compose(
        [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), normalize]
    )
    train_dataset = ImageNetWithIndices(imagenet_dir, split="val", transform=standard_test_transform)
    test_loader = torch.utils.data.DataLoader(
        train_dataset,
        batch_size=batch_size,
        num_workers=num_workers,
        # pin_memory=True,
        shuffle=False,
    )
    return test_loader
    

def get_test_dataloader(batch_size=256, num_workers=10, imagenet_dir="/mnt/disk/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_test_transform = transforms.Compose(
        [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), normalize]
    )
    train_dataset = datasets.ImageNet(imagenet_dir, split="val", transform=standard_test_transform)
    test_loader = torch.utils.data.DataLoader(
        train_dataset,
        batch_size=batch_size,
        num_workers=num_workers,
        # pin_memory=True,
        shuffle=False,
    )
    return test_loader


def calc_norms_by_channel(activations):
    # print('activations shape:\n', activations.shape)
    flattened = activations.flatten(start_dim=2)
    norms = flattened.square().mean(dim=2)

    return norms


def get_index_of_value(tensor, value, ith_match=0):
    """
    Returns generalized index (i.e. location/coordinate) of the first occurence of value
    in Tensor. For flat tensors (i.e. arrays/lists) it returns the indices of the occurrences
    of the value you are looking for. Otherwise, it returns the "index" as a coordinate.
    If there are multiple occurences then you need to choose which one you want with ith_index.
    e.g. ith_index=0 gives first occurence.

    Reference: https://stackoverflow.com/a/67175757/1601580
    :return:
    """
    # bool tensor of where value occurred
    places_where_value_occurs = tensor == value
    # get matches as a "coordinate list" where occurence happened
    matches = (tensor == value).nonzero()  # [number_of_matches, tensor_dimension]
    if matches.size(0) == 0:  # no matches
        return -1
    else:
        # get index/coordinate of the occurence you want (e.g. 1st occurence ith_match=0)
        index = matches[ith_match]
        return index


def track_image_from_index(index_tensors, image_index):
    positions = []
    for indices in index_tensors:
        positions.append(get_index_of_value(indices, image_index).item())
    return positions


def get_tuple_from_config_dict(my_dict, key):
    output = my_dict[key]
    output = output.strip("[")
    output = output.strip("]")
    output = output.split(",")
    return output


def ensure_dir(directory):
    if not os.path.exists(directory):
        os.makedirs(directory)


def track_images(sorted_channel_indices, indices_to_track):
    indices_positions = []
    for index in indices_to_track:
        indices_positions.append(track_image_from_index(sorted_channel_indices, index))

    return torch.tensor(indices_positions)


def get_attack_activations_function(attack_type):
    if attack_type == "center_neuron":
        print("Using center neuron as attack objective")

        def attack_activation_func(feature_activations):
            shape = feature_activations.shape
            # attack_act = calc_norms(feature_activations[:, channels, shape[2] // 2, shape[3] // 2])
            attack_act = feature_activations[:, :, shape[2] // 2, shape[3] // 2].square()
            return attack_act

    elif attack_type == "channel":
        print("Using channel as attack objective")

        def attack_activation_func(feature_activations):
            attack_act = calc_norms_by_channel(feature_activations)
            return attack_act

    else:
        print("ATTACK_TYPE NOT SUPPORTED. DEFAULTING TO CHANNEL")

        def attack_activation_func(feature_activations):
            attack_act = calc_norms_by_channel(feature_activations)
            return attack_act

    return attack_activation_func


def get_results_dataloader(batch_size=256, num_workers=10, imagenet_dir="/home/shared_data/imagenet", split = "train"):
    my_transform = transforms.Compose(
        [
            transforms.Resize(256),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
        ]
    )
    imagenet_data = ImageNetWithIndices(imagenet_dir, split=split, transform=my_transform)
    data_loader = torch.utils.data.DataLoader(
        imagenet_data,
        batch_size=batch_size,
        shuffle=False,
        num_workers=num_workers,
        pin_memory=False,
    )
    return data_loader


class ImageNetWithIndices(datasets.ImageNet):
    # This makes the dataloader return the dataset indices along with the standard img,label
    # means you can retrieve the original image using data_loader.dataset[index][0]
    # dataset[index] grabs the img,label,index tuple.
    def __getitem__(self, index):
        img, label = super(ImageNetWithIndices, self).__getitem__(index)
        return img, label, index


def get_topk_dataset_loader(batch_size=256, num_workers=10, imagenet_dir="/home/shared_data/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_test_transform = transforms.Compose(
        [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), normalize]
    )
    imagenet_data = ImageNetWithIndices(imagenet_dir, split="train", transform=standard_test_transform)
    data_loader = torch.utils.data.DataLoader(
        imagenet_data,
        batch_size=batch_size,
        shuffle=False,
        num_workers=num_workers,
        pin_memory=False,
    )
    return data_loader

def get_topk_dataset_loader_show(batch_size=256, num_workers=10, imagenet_dir="/home/shared_data/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_test_transform = transforms.Compose(
        [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor()]
    )
    imagenet_data = ImageNetWithIndices(imagenet_dir, split="train", transform=standard_test_transform)
    data_loader = torch.utils.data.DataLoader(
        imagenet_data,
        batch_size=batch_size,
        shuffle=False,
        num_workers=num_workers,
        pin_memory=False,
    )
    return data_loader

def get_val_topk_dataset_loader(batch_size=256, num_workers=10, imagenet_dir="/home/shared_data/imagenet"):
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    standard_test_transform = transforms.Compose(
        [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), normalize]
    )
    imagenet_data = ImageNetWithIndices(imagenet_dir, split="val", transform=standard_test_transform)
    data_loader = torch.utils.data.DataLoader(
        imagenet_data,
        batch_size=batch_size,
        shuffle=False,
        num_workers=num_workers,
        pin_memory=False,
    )
    return data_loader

# Clip encoding uses a slightly different transform and normalization than our does.
# When loading in the CLIP model it gives you a preprocess function, pass that here
def get_clip_encoding_dataloader(preprocess, batch_size=256, num_workers=10, imagenet_dir="/home/shared_data/imagenet"):
    imagenet_data = ImageNetWithIndices(imagenet_dir, split="train", transform=preprocess)
    data_loader = torch.utils.data.DataLoader(
        imagenet_data,
        batch_size=batch_size,
        shuffle=False,
        num_workers=num_workers,
        pin_memory=False,
    )
    return data_loader


def make_combined_image(channel, steps):
    # TODO have this collect all the intermediary optimal images
    path = ""
    header = ""
    size = [100, 100]

    return path, header, size


def load_in_activations(output_folder, step):
    activations_save_path = path.join(output_folder, f"model_checkpoint_activations_step_{step}.pt")
    activations = torch.load(activations_save_path)
    return activations


def save_top_class_info(class_dict, init_classes, final_classes, num_top_classes, file_path):
    labels = list(class_dict.values())
    labels = [label.split(",")[0] for label in labels]

    def get_top_labels_and_counts(classes):
        counted = torch.bincount(torch.tensor(classes))
        # print(labels)
        k_to_use = torch.min(torch.tensor([torch.tensor(classes).unique().shape[0], num_top_classes]))
        top_counts, top_indices = torch.topk(counted, k=k_to_use)

        top_labels = [labels[index] for index in top_indices]
        return top_counts, top_labels

    i_top_counts, i_top_labels = get_top_labels_and_counts(init_classes)
    f_top_counts, f_top_labels = get_top_labels_and_counts(final_classes)

    plt.subplots(figsize=(8, 8))
    plt.subplot(2, 1, 1)
    # plt.pie(i_top_counts, labels=i_top_labels, autopct=make_autopct(i_top_counts))
    plt.bar(i_top_labels, i_top_counts)
    plt.title("Initial top classes")
    plt.xticks(rotation=-20, ha="center")
    plt.subplot(2, 1, 2)

    plt.bar(f_top_labels, f_top_counts)
    plt.title("Final top classes")
    # plt.rcParams.update({'font.size': 36})
    # plt.tight_layout(pad=1.0)
    plt.subplots_adjust(hspace=0.4)
    plt.xticks(rotation=-20, ha="center")

    plt.savefig(file_path)
    plt.close()
    return i_top_labels, f_top_labels

#For saving a 2d tensor as a CSV for later perusal
def save_matrix(matrix, folder, title):
    dataframe = pd.DataFrame(matrix.cpu().numpy())
    dataframe.to_csv(path.join(folder, title+".csv"))
    return

def generate_cos_sim_graph(similiarty_tensor, channel1, channel2, title, save_path):
    graph_data = similiarty_tensor[channel1,channel2].cpu()
    fig, ax = plt.subplots()
    ax.set_title(f'Cos Sim between channels {channel1} and {channel2}{title}')
    #ax.axis('off')
    #ax.axis('tight')
    graph = ax.pcolormesh(graph_data, vmin=0, vmax=1)
    plt.ylabel('Init top image rank')
    if title == 'f':
        plt.xlabel('Final top image rank')
    else:
        plt.xlabel('Init top image ranks')

    plt.colorbar(graph)
    #ax.xlabels([2,4,6,8])
    output_path = path.join(save_path,f'cos_sim_{channel1}_{channel2}{title}.png')
    #plt.show()
    plt.savefig(output_path)
    plt.close()
    return output_path

def generate_cos_sim_csv(similiarty_tensor, channel1, channel2, title, save_path):
    csv_data = similiarty_tensor[channel1,channel2].cpu()
    output_path = path.join(save_path,'cos_sim_matrices',f'cos_sim_{channel1}_{channel2}{title}.csv')

    return

def generate_results_pdf(channel_id, paths, headers, image_dimensions, directory, title="results"):
    title_style = ParagraphStyle("title_style", fontSize=20, alignment=1)
    normal_style = ParagraphStyle("normal_style", fontSize=12, alignment=1)
    doc = SimpleDocTemplate(f"{directory}/results/channel_{channel_id}_" + title + ".pdf", pagesize=letter)

    elements = []
    elements.append(Paragraph(f"Channel {channel_id} results", style=title_style))
    elements.append(Spacer(0, 16))
    # print(image_dimensions)

    for image_path, image_header, image_dimension in zip(paths, headers, image_dimensions):
        if not path.exists(image_path + ".png"):
            print(f"{image_path}" + ".png is not a valid path!")
            continue

        # Convert the image to jpg
        # TODO Delete the jpg once it's created?

        # Add in the title

        # Add an image
        # Open the PNG image
        img = PImage.open(image_path + ".png").convert("RGB")

        # Save the image as a JPEG
        img.save(image_path + ".jpg")
        x_size, y_size = image_dimension
        elements.append(Image(image_path + ".jpg", x_size, y_size))
        elements.append(Spacer(0, 8))
        elements.append(Paragraph(image_header, style=normal_style))
        # Add a paragraph of text
    # Build the PDF
    doc.build(elements)
    return


# This function gets some details from a run's config file to access the
def get_run_config(results_directory):
    config_dict = {}
    with open(path.join(results_directory, "configuration.txt")) as f:
        for line in f:
            (key, val) = line.split(":")
            config_dict[key] = val.strip()
    # print(config_dict)
    channels = get_tuple_from_config_dict(config_dict, "channel")
    channels = [int(i) for i in channels]
    output_folder = results_directory + "/results"

    steps = [i for i in range(0, int(config_dict["nsteps"]) + 1)]
    layers = get_tuple_from_config_dict(config_dict, "layer")
    layers = [layer.strip("'") for layer in layers]
    return steps, layers, config_dict, channels, output_folder


def generate_stats_pdf(kt_dict, ndcg_dict, directory, run_name="", title="results"):
    title_style = ParagraphStyle("title_style", fontSize=20, alignment=1)
    normal_style = ParagraphStyle("normal_style", fontSize=12, alignment=1)
    doc = SimpleDocTemplate(f"{directory}/stats_" + title + ".pdf", pagesize=letter)
    elements = []
    build_doc = False
    if kt_dict is not None:
        build_doc = True
        correlations_ii = kt_dict["cor_ii"]
        correlations_if = kt_dict["cor_if"]

        elements.append(Paragraph(f"kt results {run_name}", style=title_style))
        elements.append(Spacer(0, 16))

        elements.append(
            Paragraph(f"average initial correlation between channels {correlations_ii.mean()}", style=normal_style)
        )
        elements.append(
            Paragraph(f"average final correlation between channels {correlations_if.mean()}", style=normal_style)
        )

        elements.append(
            Paragraph(f"maximum final correlation between channels {correlations_if.max()}", style=normal_style)
        )
        elements.append(
            Paragraph(
                f"found between channel(s) {correlations_if.argmax()//correlations_if.shape[0]} and {correlations_if.argmax()%correlations_if.shape[0]}",
                style=normal_style,
            )
        )
    elements.append(Spacer(0, 32))
    if ndcg_dict is not None:
        build_doc = True
        ndcg_ii = ndcg_dict["ndcg_ii"]
        ndcg_if = ndcg_dict["ndcg_if"]
        elements.append(Paragraph(f"NDCG results", style=title_style))
        elements.append(Paragraph(f"average initial NDCG between channels {ndcg_ii.mean()}", style=normal_style))
        elements.append(Paragraph(f"average final correlation between channels {ndcg_if.mean()}", style=normal_style))

        elements.append(Paragraph(f"maximum final correlation between channels {ndcg_if.max()}", style=normal_style))
        elements.append(
            Paragraph(
                f"found between channel(s) {ndcg_if.argmax() // ndcg_if.shape[0]} and {ndcg_if.argmax() % ndcg_if.shape[0]}",
                style=normal_style,
            )
        )
    if build_doc:
        doc.build(elements)


def generate_results_pdf_v2(
    channel_id, paths, headers, image_dimensions, kt_data, directory, ndcg_data=None, title="results"
):
    title_style = ParagraphStyle("title_style", fontSize=20, alignment=1)
    normal_style = ParagraphStyle("normal_style", fontSize=12, alignment=1)
    doc_path = f"{directory}/channel_{channel_id}_" + title + ".pdf"
    doc = SimpleDocTemplate(doc_path, pagesize=letter)

    elements = []
    elements.append(Paragraph(f"Channel {channel_id} results", style=title_style))
    elements.append(Spacer(0, 16))
    # print(image_dimensions)

    if kt_data is not None:
        for kt_description in kt_data:
            elements.append(Paragraph(kt_description, style=normal_style))
            elements.append(Spacer(0, 8))
    if ndcg_data is not None:
        for ndcg_description in ndcg_data:
            elements.append(Paragraph(ndcg_description, style=normal_style))
            elements.append(Spacer(0, 8))
    for image_path, image_header, image_dimension in zip(paths, headers, image_dimensions):
        x_size, y_size = image_dimension
        elements.append(Image(image_path, x_size, y_size))
        elements.append(Paragraph(image_header, style=normal_style))
        elements.append(Spacer(0, 8))
        # Add a paragraph of text
    # Build the PDF
    doc.build(elements)


## Should probably use a pandas dataframe instead?
def make_tables(table_dicts, output_path, title_size=16):
    num_plots = len(table_dicts.keys())
    fig, axs = plt.subplots(num_plots)
    for i, key in enumerate(table_dicts.keys()):
        if num_plots == 1:
            ax = axs
        else:
            ax = axs[i]
        table_dict = table_dicts[key]
        data = table_dict["data"]
        column_names = table_dict["columns"]
        title = table_dict["title"]
        # px = 1/plt.rcParams['figure.dpi']  # pixel in inches

        ax.set_title(title)
        ax.axis("off")
        ax.axis("tight")

        table = ax.table(cellText=data, colLabels=column_names, loc="best", fontsize=title_size)
        table.auto_set_font_size(False)
        table.set_fontsize(10)
        table.auto_set_column_width(col=list(range(len(column_names))))
    plt.subplots_adjust(wspace=0, hspace=0)
    plt.savefig(output_path, bbox_inches="tight")
    plt.close()
    output_path = output_path + ".png"
    return output_path


def make_table(column_names, data, title, output_path):
    fig_path = os.path.join(output_path, title)
    # px = 1/plt.rcParams['figure.dpi']  # pixel in inches
    fig, ax = plt.subplots()
    ax.set_title(title)
    ax.axis("off")
    ax.axis("tight")

    table = ax.table(cellText=data, colLabels=column_names, loc="best", fontsize=16)
    table.auto_set_font_size(False)
    table.set_fontsize(8)
    table.auto_set_column_width(col=list(range(len(column_names))))
    plt.savefig(fig_path, bbox_inches="tight")
    plt.close()
    fig_path = fig_path + ".png"
    return fig_path


def generate_results_pdf_v3(
    channel_id,
    paths,
    headers,
    image_dimensions,
    directory,
    kt_table_dict=None,
    clip_tables_dict=None,
    clip_kt_table=None,
    ndcg_data=None,
    title="results",
):
    title_style = ParagraphStyle("title_style", fontSize=20, alignment=1)
    normal_style = ParagraphStyle("normal_style", fontSize=12, alignment=1)
    doc_path = f"{directory}/channel_{channel_id}_" + title + ".pdf"
    doc = SimpleDocTemplate(doc_path, pagesize=letter)

    elements = []
    elements.append(Paragraph(f"Channel {channel_id} results", style=title_style))
    elements.append(Spacer(0, 16))
    # print(image_dimensions)

    if ndcg_data is not None:
        for ndcg_description in ndcg_data:
            elements.append(Paragraph(ndcg_description, style=normal_style))
            elements.append(Spacer(0, 8))
    images_done = 0
    for image_path, image_header, image_dimension in zip(paths, headers, image_dimensions):
        x_size, y_size = image_dimension
        elements.append(Image(image_path, x_size, y_size))
        elements.append(Paragraph(image_header, style=normal_style))
        elements.append(Spacer(0, 8))
        images_done = images_done + 1
        if images_done == 3:
            if kt_table_dict is not None:
                # for key in kt_table_dict.keys():
                #     data = kt_table_dict[key]['data']
                #     title = kt_table_dict[key]['title']
                #     columns = kt_table_dict[key]['columns']
                output_path = os.path.split(directory)[0]
                output_path = os.path.split(output_path)[0]
                output_path = os.path.join(output_path, f"channel {channel_id} kt results")
                # path = make_table(column_names=columns, data=data, title=title, output_path = os.path.join(output_path))
                kt_table_path = make_tables(kt_table_dict, output_path)
                elements.append(Image(kt_table_path, 400, 300))
                # elements.append(Spacer(0, 8))
            if clip_tables_dict is not None:
                # for key in kt_table_dict.keys():
                #     data = kt_table_dict[key]['data']
                #     title = kt_table_dict[key]['title']
                #     columns = kt_table_dict[key]['columns']
                output_path = os.path.split(directory)[0]
                output_path = os.path.split(output_path)[0]
                output_path = os.path.join(output_path, f"channel {channel_id} clip results")
                # path = make_table(column_names=columns, data=data, title=title, output_path = os.path.join(output_path))
                clip_table_path = make_tables(clip_tables_dict, output_path)
                elements.append(Image(clip_table_path, 400, 300))
                # elements.append(Spacer(0, 8))
            if clip_kt_table is not None:
                output_path = os.path.split(directory)[0]
                output_path = os.path.split(output_path)[0]
                output_path = os.path.join(output_path, f"channel {channel_id} kt clip results")
                # path = make_table(column_names=columns, data=data, title=title, output_path = os.path.join(output_path))
                kt_clip_table_path = make_tables(clip_kt_table, output_path)
                elements.append(Image(kt_clip_table_path, 400, 300))
                # Add a paragraph of text
    # Build the PDF
    doc.build(elements)


# Helper function to ge the the top images from the indices
# TODO make this faster? May not be possible to vectorize :(
# Indices should be something like (num_images,num_channels)
def get_images_from_indices(indices, num_top_images_per_channel, data_loader=None):
    # Get the dataset object associated with the dataloader
    if data_loader is None:
        dataset = get_topk_dataset_loader().dataset
    else:
        dataset = data_loader.dataset
        # print(indices.shape)

    def grab_image(idx):
        return dataset[idx][0]

    all_images = []
    for nth_place in range(num_top_images_per_channel):
        images = []
        for index in indices[nth_place]:
            image = grab_image(index)
            # print('image shape:\n', image.shape)
            images.append(image)
        images_tensor = torch.stack(images)
        all_images.append(images_tensor)
    top_image_tensor = torch.stack(all_images)
    # images = [grab_image(index) for index in indices[0]]
    # print('images from the indices look like:\n', len(images))

    # print('images from the indices look like:\n', top_image_tensor.shape)
    return top_image_tensor



def get_classes_from_indices(indices, num_top_images_per_channel, data_loader=None):
    # Get the dataset object associated with the dataloader
    if data_loader is None:
        dataset = get_topk_dataset_loader().dataset
    else:
        dataset = data_loader.dataset
        print(indices.shape)

    def grab_class(idx):
        return dataset[idx][1]

    all_classes = []
    for nth_place in range(num_top_images_per_channel):
        classes = []
        for index in indices[nth_place]:
            image = grab_class(index)
            # print('image shape:\n', image.shape)
            classes.append(image)
        classes_tensor = torch.stack(classes)
        all_classes.append(classes_tensor)
    top_classes_tensor = torch.stack(all_classes)
    # images = [grab_image(index) for index in indices[0]]
    # print('images from the indices look like:\n', len(images))

    print("classes from the indices look like:\n", top_classes_tensor.shape)
    return top_classes_tensor


# TODO Remove this as it is in utils
def get_model_layers(model):
    """
    Returns a dict with layer names as keys and layers as values
    """
    assert hasattr(model, "_modules")
    layers = {}

    def get_layers(net, prefix=[]):
        for name, layer in net._modules.items():
            if layer is None:
                continue
            if len(layer._modules) != 0:
                get_layers(layer, prefix=prefix + [name])
            else:
                layers["_".join(prefix + [name])] = layer

    get_layers(model)
    return layers


def make_position_tracking_graph(top, mid, bot, tracking_graph_path, title, steps):
    fig, ax = plt.subplots(3, 1, figsize=(10, 15))
    ax[0].set_title(title + "_top_images", fontsize=14)
    ax[0].set_xlabel("Step")
    ax[0].set_ylabel("Image Position", color="red", fontsize=14)
    ax[0].set_yscale("log")
    for positions in top:
        ax[0].plot(
            steps,
            positions,
        )
    # ax[0].legend()

    ax[1].set_title(title + "_mid_images", fontsize=14)
    ax[1].set_xlabel("Step")
    ax[1].set_ylabel("Image Position", color="red", fontsize=14)
    ax[1].set_yscale("log")
    for positions in mid:
        ax[1].plot(steps, positions)
    ax[2].set_title(title + "_bot_images", fontsize=14)
    ax[2].set_xlabel("Step")
    ax[2].set_ylabel("Image Position", color="red", fontsize=14)
    ax[2].set_yscale("log")
    for positions in bot:
        ax[2].plot(steps, positions)
    # plt.show()
    plt.savefig(tracking_graph_path)
    plt.close()


def generate_results_pdf(channel_id, paths, headers, image_dimensions, directory, title="results"):
    title_style = ParagraphStyle("title_style", fontSize=20, alignment=1)
    normal_style = ParagraphStyle("normal_style", fontSize=12, alignment=1)
    doc = SimpleDocTemplate(f"{directory}/results/channel_{channel_id}_" + title + ".pdf", pagesize=letter)

    elements = []
    elements.append(Paragraph(f"Channel {channel_id} results", style=title_style))
    elements.append(Spacer(0, 32))
    # print(image_dimensions)

    for image_path, image_header, image_dimension in zip(paths, headers, image_dimensions):
        if not path.exists(image_path + ".png"):
            print(f"{image_path}" + ".png is not a valid path!")
            continue

        # Convert the image to jpg
        # TODO Delete the jpg once it's created?

        # Add in the title

        # Add an image
        # Open the PNG image
        img = PImage.open(image_path + ".png").convert("RGB")

        # Save the image as a JPEG
        img.save(image_path + ".jpg")
        x_size, y_size = image_dimension
        elements.append(Image(image_path + ".jpg", x_size, y_size))
        elements.append(Spacer(0, 16))
        elements.append(Paragraph(image_header, style=normal_style))
        # Add a paragraph of text
    # Build the PDF
    doc.build(elements)


def get_model_activations(model, require_grad=False):
    layers = get_model_layers(model)

    activations = {layer: None for layer in layers.keys()}

    def get_activation_without_grad(layer_name):
        def hook(model, input, output):
            activations[layer_name] = output.detach()

        return hook

    def get_activation_with_grad(layer_name):
        def hook(model, input, output):
            activations[layer_name] = output

        return hook

    for name, layer in layers.items():
        if require_grad:
            layer.register_forward_hook(get_activation_with_grad(name))
        else:
            layer.register_forward_hook(get_activation_without_grad(name))

    return activations


def set_model_activations(model, require_grad=False):
    if not hasattr(model, "activations"):
        model.activations = get_model_activations(model, require_grad=require_grad)
    else:
        print("Model already has attribute 'activations'")


def get_objective_regions(model, objective_regions, require_grad=False, obj_func=None):
    # Setting up the objective function
    if obj_func is None:
        obj_func = lambda x: x
    obj_wrapper = lambda x: obj_func(x) if require_grad else obj_func(x).detach()

    layers = get_model_layers(model)
    activations = {(layer, channel): None for layer, channel, _ in objective_regions}

    # NOTE: The use of a "controller" dictionary allows us shut off hook functions when we
    # are not using them, saving on computational time and storage
    controller = {"on": True}

    def get_objective_region(layer, channel, idx):
        def hook(model, input, output):
            if controller["on"]:
                if idx is None:
                    activations[(layer, channel)] = obj_wrapper(output[:, channel])
                elif max(idx) < output.shape[0]:
                    activations[(layer, channel)] = obj_wrapper(output[idx, channel])
                else:
                    activations[(layer, channel)] = None
            else:
                activations[(layer, channel)] = None

        return hook

    for layer, channel, idx in objective_regions:
        layers[layer].register_forward_hook(get_objective_region(layer, channel, idx))

    return activations, controller


# TODO: Remove this if it's completely useless
def get_objective_regions_old(
    model, objective_regions, maintain=False, require_grad=True, norm=lambda x: torch.norm(x, p=2)
):
    layers = get_model_layers(model)
    activations = {(layer, channel): None for layer, channel, _ in objective_regions}

    # NOTE: The use of a "controller" dictionary allows us shut off hook functions when we
    # are not using them, saving on computational time and storage
    controller = {"on": True}

    def get_objective_region(layer, channel, idx, norm=None):
        if norm is None:
            norm = lambda x: x

        # All sorts of functions depending on the arguments to get_objective_regions
        def get_obj(model, input, output):
            activations[(layer, channel)] = norm(output[idx, channel]).detach()

        def get_obj_with_grad(model, input, output):
            activations[(layer, channel)] = norm(output[idx, channel])

        def get_obj_maintain(model, input, output):
            activtions[(layer, channel)] = output[:, channel].detach()

        def get_obj_maintain_with_grad(model, input, output):
            activations[(layer, channel)] = output[:, channel]

        if maintain and require_grad:
            my_obj = get_obj_maintain_with_grad
        elif (not maintain) and require_grad:
            my_obj = get_obj_with_grad
        elif maintain and (not require_grad):
            my_obj = get_obj_maintain
        else:
            my_obj = get_obj

        def hook(model, input, output):
            if controller["on"]:
                activations[(layer, channel)] = my_obj(model, input, output)
            else:
                activations[(layer, channel)] = None

        return hook

    for layer, channel, idx in objective_regions:
        layers[layer].register_forward_hook(get_objective_region(layer, channel, idx, norm=norm))

    return activations, controller


def get_model_activations_hook(*args, **kwargs):
    activations = get_model_activations(*args, **kwargs)
    dummy_image = torch.zeros((1, 3, 224, 224))
    dummy_labels = torch.zeros(1000)

    def hook(layer):
        if layer == "input":
            return dummy_image
        elif layer == "labels":
            return dummy_labels
        elif layer in activations.keys():
            return activations[layer]
        else:
            raise Exception(f"No layer {layer} in model")

    return hook


def get_receptive_field(model, layer, channel, x, y, input_size=224):
    feature_net = model.features[:layer]

    test_img = torch.ones((1, 3, input_size, input_size), dtype=torch.float, requires_grad=True)
    test_img_activation = feature_net(test_img)

    neuron_activation = test_img_activation[0, channel, x, y]
    neuron_activation.backward()

    grad_img = test_img.grad[0].detach().numpy().transpose(1, 2, 0)

    return (grad_img != 0).astype(np.float32)


def determine_output_channels(layer):
    try:
        return layer.out_channels
    except:
        return None


def lucent_image_custom(w, h=None, sd=None, batch=None, decorrelate=True, fft=True, channels=None, param_f=None):
    h = h or w
    batch = batch or 1
    ch = channels or 3
    shape = [batch, ch, h, w]

    if param_f is None:
        param_f = fft_image if fft else pixel_image
    params, image_f = param_f(shape)

    # TODO: Figure out what "channels" is supposed to specify
    if channels:
        output = to_valid_rgb(image_f, decorrelate=False)
    else:
        output = to_valid_rgb(image_f, decorrelate=decorrelate)
    return params, output


def pixel_image_custom(shape, dist, decay_power=1, device=None):
    device = device or torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    tensor = dist(*shape).to(device).requires_grad_(True)
    return [tensor], lambda: tensor


def fft_image_custom(shape, dist, device=None):
    device = device or torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    batch, channels, h, w = shape
    freqs = rfft2d_freqs(h, w)
    init_val_size = (batch, channels) + freqs.shape + (2,)  # for imaginary components

    spectrum_real_imag_t = dist(*init_val_size, freqs).to(device).requires_grad_(True)

    scale = np.minimum(1.0 / freqs, max(h, w)) ** decay_power
    scale = torch.tensor(scale).float()[None, None, ..., None].to(device)

    scaled_spectrum_t = scale * spectrum_real_imag_t
    if TORCH_VERSION >= "1.7.0":
        import torch.fft

        if type(scaled_spectrum_t) is not torch.complex64:
            scaled_spectrum_t = torch.view_as_complex(scaled_spectrum_t)
        image = torch.fft.irfftn(scaled_spectrum_t, s=(h, w), norm="ortho")
    else:
        import torch

        image = torch.irfft(scaled_spectrum_t, 2, normalized=True, signal_sizes=(h, w))
    image = image[:batch, :channels, :h, :w]
    magic = 4.0  # Magic constant from Lucid
    image = image / magic
    return [spectrum_real_imag_t], lambda: image


def uniform_pixel_dist(low=0.0, high=1.0):
    return lambda *shape: torch.empty(*shape).uniform_(low, high).type(torch.FloatTensor)


def bright_dot_in_middle(mu=0.0, sigma=1.0):
    import scipy.stats

    def inner(*shape):
        normal_dist = scipy.stats.norm(mu, sigma)
        output = torch.ones(*shape)
        nrows, ncols = output.shape[2:]
        midrow, midcol = nrows // 2, ncols // 2
        for row in range(nrows):
            for col in range(ncols):
                distance = np.linalg.norm([row - midrow, col - midcol])
                output[..., row, col] = normal_dist.pdf(distance)
        return output

    return inner


def frequency_distribution(freq_sigmas):
    pass


def get_layer_parameters(model, layer_name):
    for name, param in model.named_parameters():
        if layer_name.replace("_", ".") + "." in name:
            yield param


def recreate_state_dict(directory, step):
    state_dict = OrderedDict()
    for filename in os.listdir(directory):
        filepath = os.path.join(directory, filename)
        attributes = filename.split(".")
        if int(attributes[0]) != step:
            continue
        key = ".".join(attributes[1:4])
        parameter = torch.load(filepath)
        state_dict[key] = parameter
    return state_dict


def _flatten(arr):
    if type(arr) is list and type(arr[0]) is list:
        return list(chain(*[_flatten(el) for el in arr]))
    else:
        return arr


def generate_kt_pdf(kt_dict, directory, title="results"):
    correlations_ii = kt_dict["cor_ii"]
    correlations_if = kt_dict["cor_if"]
    pvalues_ii = kt_dict["p_ii"]
    pvalues_if = kt_dict["p_if"]

    title_style = ParagraphStyle("title_style", fontSize=20, alignment=1)
    normal_style = ParagraphStyle("normal_style", fontSize=12, alignment=1)
    doc = SimpleDocTemplate(f"{directory}/channel_overall_kt_" + title + ".pdf", pagesize=letter)

    elements = []

    elements.append(Paragraph(f"kt results", style=title_style))
    elements.append(Spacer(0, 16))
    elements.append(
        Paragraph(
            f"average maximum correlation between channels initially {correlations_ii.max(dim=1)[0].mean()}",
            style=normal_style,
        )
    )
    elements.append(
        Paragraph(
            f"average maximum correlation between channels finally {correlations_if.max(dim=1)[0].mean()}",
            style=normal_style,
        )
    )
    elements.append(Spacer(0, 16))
    elements.append(
        Paragraph(f"average initial correlation between channels {correlations_ii.mean()}", style=normal_style)
    )
    elements.append(
        Paragraph(f"average final correlation between channels {correlations_if.mean()}", style=normal_style)
    )
    elements.append(
        Paragraph(f"maximum final correlation between channels {correlations_if.max()}", style=normal_style)
    )
    doc.build(elements)


# save_args outputs a copy of the args used to run the script that calls it.
def save_args(arg_parser, optimizer_dict, version="<version>"):
    if arg_parser.output is not None:
        output_file = str(arg_parser.output) + "/configuration.txt"
    else:
        output_file = r"../" + "configuration.txt"

    with open(output_file, "w") as f:
        for arg in vars(arg_parser):
            f.write((str(arg) + ": " + str(getattr(arg_parser, arg))))
            f.write("\n")
        for key in optimizer_dict.keys():
            f.write(key + ": " + str(optimizer_dict[key]) + "\n")
        f.write("version: " + version)


def get_init_final_models(results_directory, model_arch = 'alexnet', device="cuda"):
    print('getting init and final models')
    print(f'model arch: {model_arch}')
    if model_arch == 'alexnet':
        init_model = models.alexnet(weights=models.AlexNet_Weights.IMAGENET1K_V1).to(device)
        final_model = models.alexnet()
    elif model_arch == "resnet50":
        init_model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
        final_model = models.resnet50()
    elif model_arch == "resnet18":
        init_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
        final_model = models.resnet18()
    elif model_arch == 'efficientnet':
        init_model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.IMAGENET1K_V1)
        final_model = models.efficientnet_b0()

    state_dict = torch.load(path.join(results_directory, "final_model.pt"))
    final_model.load_state_dict(state_dict)
    init_model.eval()
    final_model.eval()
    return init_model.to(device), final_model.to(device)


def register_hooks(model_layers, features, optim_layer, FEATURE_NAME="layer_features"):
    def get_features(name):
        def hook(model, input, output):
            features[name] = output  # .detach()

        return hook
    print()
    model_layers[optim_layer[0]].register_forward_hook(get_features(FEATURE_NAME))


def get_model_layers(model):
    """
    Returns a dict with layer names as keys and layers as values
    """
    assert hasattr(model, "_modules")
    layers = {}

    def get_layers(net, prefix=[]):
        for name, layer in net._modules.items():
            if layer is None:
                continue
            if len(layer._modules) != 0:
                get_layers(layer, prefix=prefix + [name])
            else:
                layers["_".join(prefix + [name])] = layer

    get_layers(model)
    return layers


def get_class_label_dict():
    return get_class_labels_dict()

def unpack_config_file_into_dict(results_directory):
    config_dict = {}
    with open(path.join(results_directory, "configuration.txt")) as f:
        for line in f:
            (key, val) = line.split(':')[0], line.split(':')[1:]
            if len(val) ==1:
                config_dict[key] = val[0].strip()
            else:
                s = ""
                for v in val:
                    s = s + v + ':'
                config_dict[key] = s[:-2]
    return config_dict


def get_class_labels_dict():
    class_label_dict = {
        0: "tench, Tinca tinca",
        1: "goldfish, Carassius auratus",
        2: "great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias",
        3: "tiger shark, Galeocerdo cuvieri",
        4: "hammerhead, hammerhead shark",
        5: "electric ray, crampfish, numbfish, torpedo",
        6: "stingray",
        7: "cock",
        8: "hen",
        9: "ostrich, Struthio camelus",
        10: "brambling, Fringilla montifringilla",
        11: "goldfinch, Carduelis carduelis",
        12: "house finch, linnet, Carpodacus mexicanus",
        13: "junco, snowbird",
        14: "indigo bunting, indigo finch, indigo bird, Passerina cyanea",
        15: "robin, American robin, Turdus migratorius",
        16: "bulbul",
        17: "jay",
        18: "magpie",
        19: "chickadee",
        20: "water ouzel, dipper",
        21: "kite",
        22: "bald eagle, American eagle, Haliaeetus leucocephalus",
        23: "vulture",
        24: "great grey owl, great gray owl, Strix nebulosa",
        25: "European fire salamander, Salamandra salamandra",
        26: "common newt, Triturus vulgaris",
        27: "eft",
        28: "spotted salamander, Ambystoma maculatum",
        29: "axolotl, mud puppy, Ambystoma mexicanum",
        30: "bullfrog, Rana catesbeiana",
        31: "tree frog, tree-frog",
        32: "tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui",
        33: "loggerhead, loggerhead turtle, Caretta caretta",
        34: "leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea",
        35: "mud turtle",
        36: "terrapin",
        37: "box turtle, box tortoise",
        38: "banded gecko",
        39: "common iguana, iguana, Iguana iguana",
        40: "American chameleon, anole, Anolis carolinensis",
        41: "whiptail, whiptail lizard",
        42: "agama",
        43: "frilled lizard, Chlamydosaurus kingi",
        44: "alligator lizard",
        45: "Gila monster, Heloderma suspectum",
        46: "green lizard, Lacerta viridis",
        47: "African chameleon, Chamaeleo chamaeleon",
        48: "Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis",
        49: "African crocodile, Nile crocodile, Crocodylus niloticus",
        50: "American alligator, Alligator mississipiensis",
        51: "triceratops",
        52: "thunder snake, worm snake, Carphophis amoenus",
        53: "ringneck snake, ring-necked snake, ring snake",
        54: "hognose snake, puff adder, sand viper",
        55: "green snake, grass snake",
        56: "king snake, kingsnake",
        57: "garter snake, grass snake",
        58: "water snake",
        59: "vine snake",
        60: "night snake, Hypsiglena torquata",
        61: "boa constrictor, Constrictor constrictor",
        62: "rock python, rock snake, Python sebae",
        63: "Indian cobra, Naja naja",
        64: "green mamba",
        65: "sea snake",
        66: "horned viper, cerastes, sand viper, horned asp, Cerastes cornutus",
        67: "diamondback, diamondback rattlesnake, Crotalus adamanteus",
        68: "sidewinder, horned rattlesnake, Crotalus cerastes",
        69: "trilobite",
        70: "harvestman, daddy longlegs, Phalangium opilio",
        71: "scorpion",
        72: "black and gold garden spider, Argiope aurantia",
        73: "barn spider, Araneus cavaticus",
        74: "garden spider, Aranea diademata",
        75: "black widow, Latrodectus mactans",
        76: "tarantula",
        77: "wolf spider, hunting spider",
        78: "tick",
        79: "centipede",
        80: "black grouse",
        81: "ptarmigan",
        82: "ruffed grouse, partridge, Bonasa umbellus",
        83: "prairie chicken, prairie grouse, prairie fowl",
        84: "peacock",
        85: "quail",
        86: "partridge",
        87: "African grey, African gray, Psittacus erithacus",
        88: "macaw",
        89: "sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita",
        90: "lorikeet",
        91: "coucal",
        92: "bee eater",
        93: "hornbill",
        94: "hummingbird",
        95: "jacamar",
        96: "toucan",
        97: "drake",
        98: "red-breasted merganser, Mergus serrator",
        99: "goose",
        100: "black swan, Cygnus atratus",
        101: "tusker",
        102: "echidna, spiny anteater, anteater",
        103: "platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus",
        104: "wallaby, brush kangaroo",
        105: "koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus",
        106: "wombat",
        107: "jellyfish",
        108: "sea anemone, anemone",
        109: "brain coral",
        110: "flatworm, platyhelminth",
        111: "nematode, nematode worm, roundworm",
        112: "conch",
        113: "snail",
        114: "slug",
        115: "sea slug, nudibranch",
        116: "chiton, coat-of-mail shell, sea cradle, polyplacophore",
        117: "chambered nautilus, pearly nautilus, nautilus",
        118: "Dungeness crab, Cancer magister",
        119: "rock crab, Cancer irroratus",
        120: "fiddler crab",
        121: "king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica",
        122: "American lobster, Northern lobster, Maine lobster, Homarus americanus",
        123: "spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish",
        124: "crayfish, crawfish, crawdad, crawdaddy",
        125: "hermit crab",
        126: "isopod",
        127: "white stork, Ciconia ciconia",
        128: "black stork, Ciconia nigra",
        129: "spoonbill",
        130: "flamingo",
        131: "little blue heron, Egretta caerulea",
        132: "American egret, great white heron, Egretta albus",
        133: "bittern",
        134: "crane",
        135: "limpkin, Aramus pictus",
        136: "European gallinule, Porphyrio porphyrio",
        137: "American coot, marsh hen, mud hen, water hen, Fulica americana",
        138: "bustard",
        139: "ruddy turnstone, Arenaria interpres",
        140: "red-backed sandpiper, dunlin, Erolia alpina",
        141: "redshank, Tringa totanus",
        142: "dowitcher",
        143: "oystercatcher, oyster catcher",
        144: "pelican",
        145: "king penguin, Aptenodytes patagonica",
        146: "albatross, mollymawk",
        147: "grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus",
        148: "killer whale, killer, orca, grampus, sea wolf, Orcinus orca",
        149: "dugong, Dugong dugon",
        150: "sea lion",
        151: "Chihuahua",
        152: "Japanese spaniel",
        153: "Maltese dog, Maltese terrier, Maltese",
        154: "Pekinese, Pekingese, Peke",
        155: "Shih-Tzu",
        156: "Blenheim spaniel",
        157: "papillon",
        158: "toy terrier",
        159: "Rhodesian ridgeback",
        160: "Afghan hound, Afghan",
        161: "basset, basset hound",
        162: "beagle",
        163: "bloodhound, sleuthhound",
        164: "bluetick",
        165: "black-and-tan coonhound",
        166: "Walker hound, Walker foxhound",
        167: "English foxhound",
        168: "redbone",
        169: "borzoi, Russian wolfhound",
        170: "Irish wolfhound",
        171: "Italian greyhound",
        172: "whippet",
        173: "Ibizan hound, Ibizan Podenco",
        174: "Norwegian elkhound, elkhound",
        175: "otterhound, otter hound",
        176: "Saluki, gazelle hound",
        177: "Scottish deerhound, deerhound",
        178: "Weimaraner",
        179: "Staffordshire bullterrier, Staffordshire bull terrier",
        180: "American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier",
        181: "Bedlington terrier",
        182: "Border terrier",
        183: "Kerry blue terrier",
        184: "Irish terrier",
        185: "Norfolk terrier",
        186: "Norwich terrier",
        187: "Yorkshire terrier",
        188: "wire-haired fox terrier",
        189: "Lakeland terrier",
        190: "Sealyham terrier, Sealyham",
        191: "Airedale, Airedale terrier",
        192: "cairn, cairn terrier",
        193: "Australian terrier",
        194: "Dandie Dinmont, Dandie Dinmont terrier",
        195: "Boston bull, Boston terrier",
        196: "miniature schnauzer",
        197: "giant schnauzer",
        198: "standard schnauzer",
        199: "Scotch terrier, Scottish terrier, Scottie",
        200: "Tibetan terrier, chrysanthemum dog",
        201: "silky terrier, Sydney silky",
        202: "soft-coated wheaten terrier",
        203: "West Highland white terrier",
        204: "Lhasa, Lhasa apso",
        205: "flat-coated retriever",
        206: "curly-coated retriever",
        207: "golden retriever",
        208: "Labrador retriever",
        209: "Chesapeake Bay retriever",
        210: "German short-haired pointer",
        211: "vizsla, Hungarian pointer",
        212: "English setter",
        213: "Irish setter, red setter",
        214: "Gordon setter",
        215: "Brittany spaniel",
        216: "clumber, clumber spaniel",
        217: "English springer, English springer spaniel",
        218: "Welsh springer spaniel",
        219: "cocker spaniel, English cocker spaniel, cocker",
        220: "Sussex spaniel",
        221: "Irish water spaniel",
        222: "kuvasz",
        223: "schipperke",
        224: "groenendael",
        225: "malinois",
        226: "briard",
        227: "kelpie",
        228: "komondor",
        229: "Old English sheepdog, bobtail",
        230: "Shetland sheepdog, Shetland sheep dog, Shetland",
        231: "collie",
        232: "Border collie",
        233: "Bouvier des Flandres, Bouviers des Flandres",
        234: "Rottweiler",
        235: "German shepherd, German shepherd dog, German police dog, alsatian",
        236: "Doberman, Doberman pinscher",
        237: "miniature pinscher",
        238: "Greater Swiss Mountain dog",
        239: "Bernese mountain dog",
        240: "Appenzeller",
        241: "EntleBucher",
        242: "boxer",
        243: "bull mastiff",
        244: "Tibetan mastiff",
        245: "French bulldog",
        246: "Great Dane",
        247: "Saint Bernard, St Bernard",
        248: "Eskimo dog, husky",
        249: "malamute, malemute, Alaskan malamute",
        250: "Siberian husky",
        251: "dalmatian, coach dog, carriage dog",
        252: "affenpinscher, monkey pinscher, monkey dog",
        253: "basenji",
        254: "pug, pug-dog",
        255: "Leonberg",
        256: "Newfoundland, Newfoundland dog",
        257: "Great Pyrenees",
        258: "Samoyed, Samoyede",
        259: "Pomeranian",
        260: "chow, chow chow",
        261: "keeshond",
        262: "Brabancon griffon",
        263: "Pembroke, Pembroke Welsh corgi",
        264: "Cardigan, Cardigan Welsh corgi",
        265: "toy poodle",
        266: "miniature poodle",
        267: "standard poodle",
        268: "Mexican hairless",
        269: "timber wolf, grey wolf, gray wolf, Canis lupus",
        270: "white wolf, Arctic wolf, Canis lupus tundrarum",
        271: "red wolf, maned wolf, Canis rufus, Canis niger",
        272: "coyote, prairie wolf, brush wolf, Canis latrans",
        273: "dingo, warrigal, warragal, Canis dingo",
        274: "dhole, Cuon alpinus",
        275: "African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus",
        276: "hyena, hyaena",
        277: "red fox, Vulpes vulpes",
        278: "kit fox, Vulpes macrotis",
        279: "Arctic fox, white fox, Alopex lagopus",
        280: "grey fox, gray fox, Urocyon cinereoargenteus",
        281: "tabby, tabby cat",
        282: "tiger cat",
        283: "Persian cat",
        284: "Siamese cat, Siamese",
        285: "Egyptian cat",
        286: "cougar, puma, catamount, mountain lion, painter, panther, Felis concolor",
        287: "lynx, catamount",
        288: "leopard, Panthera pardus",
        289: "snow leopard, ounce, Panthera uncia",
        290: "jaguar, panther, Panthera onca, Felis onca",
        291: "lion, king of beasts, Panthera leo",
        292: "tiger, Panthera tigris",
        293: "cheetah, chetah, Acinonyx jubatus",
        294: "brown bear, bruin, Ursus arctos",
        295: "American black bear, black bear, Ursus americanus, Euarctos americanus",
        296: "ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus",
        297: "sloth bear, Melursus ursinus, Ursus ursinus",
        298: "mongoose",
        299: "meerkat, mierkat",
        300: "tiger beetle",
        301: "ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle",
        302: "ground beetle, carabid beetle",
        303: "long-horned beetle, longicorn, longicorn beetle",
        304: "leaf beetle, chrysomelid",
        305: "dung beetle",
        306: "rhinoceros beetle",
        307: "weevil",
        308: "fly",
        309: "bee",
        310: "ant, emmet, pismire",
        311: "grasshopper, hopper",
        312: "cricket",
        313: "walking stick, walkingstick, stick insect",
        314: "cockroach, roach",
        315: "mantis, mantid",
        316: "cicada, cicala",
        317: "leafhopper",
        318: "lacewing, lacewing fly",
        319: "dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk",
        320: "damselfly",
        321: "admiral",
        322: "ringlet, ringlet butterfly",
        323: "monarch, monarch butterfly, milkweed butterfly, Danaus plexippus",
        324: "cabbage butterfly",
        325: "sulphur butterfly, sulfur butterfly",
        326: "lycaenid, lycaenid butterfly",
        327: "starfish, sea star",
        328: "sea urchin",
        329: "sea cucumber, holothurian",
        330: "wood rabbit, cottontail, cottontail rabbit",
        331: "hare",
        332: "Angora, Angora rabbit",
        333: "hamster",
        334: "porcupine, hedgehog",
        335: "fox squirrel, eastern fox squirrel, Sciurus niger",
        336: "marmot",
        337: "beaver",
        338: "guinea pig, Cavia cobaya",
        339: "sorrel",
        340: "zebra",
        341: "hog, pig, grunter, squealer, Sus scrofa",
        342: "wild boar, boar, Sus scrofa",
        343: "warthog",
        344: "hippopotamus, hippo, river horse, Hippopotamus amphibius",
        345: "ox",
        346: "water buffalo, water ox, Asiatic buffalo, Bubalus bubalis",
        347: "bison",
        348: "ram, tup",
        349: "bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis",
        350: "ibex, Capra ibex",
        351: "hartebeest",
        352: "impala, Aepyceros melampus",
        353: "gazelle",
        354: "Arabian camel, dromedary, Camelus dromedarius",
        355: "llama",
        356: "weasel",
        357: "mink",
        358: "polecat, fitch, foulmart, foumart, Mustela putorius",
        359: "black-footed ferret, ferret, Mustela nigripes",
        360: "otter",
        361: "skunk, polecat, wood pussy",
        362: "badger",
        363: "armadillo",
        364: "three-toed sloth, ai, Bradypus tridactylus",
        365: "orangutan, orang, orangutang, Pongo pygmaeus",
        366: "gorilla, Gorilla gorilla",
        367: "chimpanzee, chimp, Pan troglodytes",
        368: "gibbon, Hylobates lar",
        369: "siamang, Hylobates syndactylus, Symphalangus syndactylus",
        370: "guenon, guenon monkey",
        371: "patas, hussar monkey, Erythrocebus patas",
        372: "baboon",
        373: "macaque",
        374: "langur",
        375: "colobus, colobus monkey",
        376: "proboscis monkey, Nasalis larvatus",
        377: "marmoset",
        378: "capuchin, ringtail, Cebus capucinus",
        379: "howler monkey, howler",
        380: "titi, titi monkey",
        381: "spider monkey, Ateles geoffroyi",
        382: "squirrel monkey, Saimiri sciureus",
        383: "Madagascar cat, ring-tailed lemur, Lemur catta",
        384: "indri, indris, Indri indri, Indri brevicaudatus",
        385: "Indian elephant, Elephas maximus",
        386: "African elephant, Loxodonta africana",
        387: "lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens",
        388: "giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca",
        389: "barracouta, snoek",
        390: "eel",
        391: "coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch",
        392: "rock beauty, Holocanthus tricolor",
        393: "anemone fish",
        394: "sturgeon",
        395: "gar, garfish, garpike, billfish, Lepisosteus osseus",
        396: "lionfish",
        397: "puffer, pufferfish, blowfish, globefish",
        398: "abacus",
        399: "abaya",
        400: "academic gown, academic robe, judge's robe",
        401: "accordion, piano accordion, squeeze box",
        402: "acoustic guitar",
        403: "aircraft carrier, carrier, flattop, attack aircraft carrier",
        404: "airliner",
        405: "airship, dirigible",
        406: "altar",
        407: "ambulance",
        408: "amphibian, amphibious vehicle",
        409: "analog clock",
        410: "apiary, bee house",
        411: "apron",
        412: "ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin",
        413: "assault rifle, assault gun",
        414: "backpack, back pack, knapsack, packsack, rucksack, haversack",
        415: "bakery, bakeshop, bakehouse",
        416: "balance beam, beam",
        417: "balloon",
        418: "ballpoint, ballpoint pen, ballpen, Biro",
        419: "Band Aid",
        420: "banjo",
        421: "bannister, banister, balustrade, balusters, handrail",
        422: "barbell",
        423: "barber chair",
        424: "barbershop",
        425: "barn",
        426: "barometer",
        427: "barrel, cask",
        428: "barrow, garden cart, lawn cart, wheelbarrow",
        429: "baseball",
        430: "basketball",
        431: "bassinet",
        432: "bassoon",
        433: "bathing cap, swimming cap",
        434: "bath towel",
        435: "bathtub, bathing tub, bath, tub",
        436: "beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon",
        437: "beacon, lighthouse, beacon light, pharos",
        438: "beaker",
        439: "bearskin, busby, shako",
        440: "beer bottle",
        441: "beer glass",
        442: "bell cote, bell cot",
        443: "bib",
        444: "bicycle-built-for-two, tandem bicycle, tandem",
        445: "bikini, two-piece",
        446: "binder, ring-binder",
        447: "binoculars, field glasses, opera glasses",
        448: "birdhouse",
        449: "boathouse",
        450: "bobsled, bobsleigh, bob",
        451: "bolo tie, bolo, bola tie, bola",
        452: "bonnet, poke bonnet",
        453: "bookcase",
        454: "bookshop, bookstore, bookstall",
        455: "bottlecap",
        456: "bow",
        457: "bow tie, bow-tie, bowtie",
        458: "brass, memorial tablet, plaque",
        459: "brassiere, bra, bandeau",
        460: "breakwater, groin, groyne, mole, bulwark, seawall, jetty",
        461: "breastplate, aegis, egis",
        462: "broom",
        463: "bucket, pail",
        464: "buckle",
        465: "bulletproof vest",
        466: "bullet train, bullet",
        467: "butcher shop, meat market",
        468: "cab, hack, taxi, taxicab",
        469: "caldron, cauldron",
        470: "candle, taper, wax light",
        471: "cannon",
        472: "canoe",
        473: "can opener, tin opener",
        474: "cardigan",
        475: "car mirror",
        476: "carousel, carrousel, merry-go-round, roundabout, whirligig",
        477: "carpenter's kit, tool kit",
        478: "carton",
        479: "car wheel",
        480: "cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM",
        481: "cassette",
        482: "cassette player",
        483: "castle",
        484: "catamaran",
        485: "CD player",
        486: "cello, violoncello",
        487: "cellular telephone, cellular phone, cellphone, cell, mobile phone",
        488: "chain",
        489: "chainlink fence",
        490: "chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour",
        491: "chain saw, chainsaw",
        492: "chest",
        493: "chiffonier, commode",
        494: "chime, bell, gong",
        495: "china cabinet, china closet",
        496: "Christmas stocking",
        497: "church, church building",
        498: "cinema, movie theater, movie theatre, movie house, picture palace",
        499: "cleaver, meat cleaver, chopper",
        500: "cliff dwelling",
        501: "cloak",
        502: "clog, geta, patten, sabot",
        503: "cocktail shaker",
        504: "coffee mug",
        505: "coffeepot",
        506: "coil, spiral, volute, whorl, helix",
        507: "combination lock",
        508: "computer keyboard, keypad",
        509: "confectionery, confectionary, candy store",
        510: "container ship, containership, container vessel",
        511: "convertible",
        512: "corkscrew, bottle screw",
        513: "cornet, horn, trumpet, trump",
        514: "cowboy boot",
        515: "cowboy hat, ten-gallon hat",
        516: "cradle",
        517: "crane",
        518: "crash helmet",
        519: "crate",
        520: "crib, cot",
        521: "Crock Pot",
        522: "croquet ball",
        523: "crutch",
        524: "cuirass",
        525: "dam, dike, dyke",
        526: "desk",
        527: "desktop computer",
        528: "dial telephone, dial phone",
        529: "diaper, nappy, napkin",
        530: "digital clock",
        531: "digital watch",
        532: "dining table, board",
        533: "dishrag, dishcloth",
        534: "dishwasher, dish washer, dishwashing machine",
        535: "disk brake, disc brake",
        536: "dock, dockage, docking facility",
        537: "dogsled, dog sled, dog sleigh",
        538: "dome",
        539: "doormat, welcome mat",
        540: "drilling platform, offshore rig",
        541: "drum, membranophone, tympan",
        542: "drumstick",
        543: "dumbbell",
        544: "Dutch oven",
        545: "electric fan, blower",
        546: "electric guitar",
        547: "electric locomotive",
        548: "entertainment center",
        549: "envelope",
        550: "espresso maker",
        551: "face powder",
        552: "feather boa, boa",
        553: "file, file cabinet, filing cabinet",
        554: "fireboat",
        555: "fire engine, fire truck",
        556: "fire screen, fireguard",
        557: "flagpole, flagstaff",
        558: "flute, transverse flute",
        559: "folding chair",
        560: "football helmet",
        561: "forklift",
        562: "fountain",
        563: "fountain pen",
        564: "four-poster",
        565: "freight car",
        566: "French horn, horn",
        567: "frying pan, frypan, skillet",
        568: "fur coat",
        569: "garbage truck, dustcart",
        570: "gasmask, respirator, gas helmet",
        571: "gas pump, gasoline pump, petrol pump, island dispenser",
        572: "goblet",
        573: "go-kart",
        574: "golf ball",
        575: "golfcart, golf cart",
        576: "gondola",
        577: "gong, tam-tam",
        578: "gown",
        579: "grand piano, grand",
        580: "greenhouse, nursery, glasshouse",
        581: "grille, radiator grille",
        582: "grocery store, grocery, food market, market",
        583: "guillotine",
        584: "hair slide",
        585: "hair spray",
        586: "half track",
        587: "hammer",
        588: "hamper",
        589: "hand blower, blow dryer, blow drier, hair dryer, hair drier",
        590: "hand-held computer, hand-held microcomputer",
        591: "handkerchief, hankie, hanky, hankey",
        592: "hard disc, hard disk, fixed disk",
        593: "harmonica, mouth organ, harp, mouth harp",
        594: "harp",
        595: "harvester, reaper",
        596: "hatchet",
        597: "holster",
        598: "home theater, home theatre",
        599: "honeycomb",
        600: "hook, claw",
        601: "hoopskirt, crinoline",
        602: "horizontal bar, high bar",
        603: "horse cart, horse-cart",
        604: "hourglass",
        605: "iPod",
        606: "iron, smoothing iron",
        607: "jack-o'-lantern",
        608: "jean, blue jean, denim",
        609: "jeep, landrover",
        610: "jersey, T-shirt, tee shirt",
        611: "jigsaw puzzle",
        612: "jinrikisha, ricksha, rickshaw",
        613: "joystick",
        614: "kimono",
        615: "knee pad",
        616: "knot",
        617: "lab coat, laboratory coat",
        618: "ladle",
        619: "lampshade, lamp shade",
        620: "laptop, laptop computer",
        621: "lawn mower, mower",
        622: "lens cap, lens cover",
        623: "letter opener, paper knife, paperknife",
        624: "library",
        625: "lifeboat",
        626: "lighter, light, igniter, ignitor",
        627: "limousine, limo",
        628: "liner, ocean liner",
        629: "lipstick, lip rouge",
        630: "Loafer",
        631: "lotion",
        632: "loudspeaker, speaker, speaker unit, loudspeaker system, speaker system",
        633: "loupe, jeweler's loupe",
        634: "lumbermill, sawmill",
        635: "magnetic compass",
        636: "mailbag, postbag",
        637: "mailbox, letter box",
        638: "maillot",
        639: "maillot, tank suit",
        640: "manhole cover",
        641: "maraca",
        642: "marimba, xylophone",
        643: "mask",
        644: "matchstick",
        645: "maypole",
        646: "maze, labyrinth",
        647: "measuring cup",
        648: "medicine chest, medicine cabinet",
        649: "megalith, megalithic structure",
        650: "microphone, mike",
        651: "microwave, microwave oven",
        652: "military uniform",
        653: "milk can",
        654: "minibus",
        655: "miniskirt, mini",
        656: "minivan",
        657: "missile",
        658: "mitten",
        659: "mixing bowl",
        660: "mobile home, manufactured home",
        661: "Model T",
        662: "modem",
        663: "monastery",
        664: "monitor",
        665: "moped",
        666: "mortar",
        667: "mortarboard",
        668: "mosque",
        669: "mosquito net",
        670: "motor scooter, scooter",
        671: "mountain bike, all-terrain bike, off-roader",
        672: "mountain tent",
        673: "mouse, computer mouse",
        674: "mousetrap",
        675: "moving van",
        676: "muzzle",
        677: "nail",
        678: "neck brace",
        679: "necklace",
        680: "nipple",
        681: "notebook, notebook computer",
        682: "obelisk",
        683: "oboe, hautboy, hautbois",
        684: "ocarina, sweet potato",
        685: "odometer, hodometer, mileometer, milometer",
        686: "oil filter",
        687: "organ, pipe organ",
        688: "oscilloscope, scope, cathode-ray oscilloscope, CRO",
        689: "overskirt",
        690: "oxcart",
        691: "oxygen mask",
        692: "packet",
        693: "paddle, boat paddle",
        694: "paddlewheel, paddle wheel",
        695: "padlock",
        696: "paintbrush",
        697: "pajama, pyjama, pj's, jammies",
        698: "palace",
        699: "panpipe, pandean pipe, syrinx",
        700: "paper towel",
        701: "parachute, chute",
        702: "parallel bars, bars",
        703: "park bench",
        704: "parking meter",
        705: "passenger car, coach, carriage",
        706: "patio, terrace",
        707: "pay-phone, pay-station",
        708: "pedestal, plinth, footstall",
        709: "pencil box, pencil case",
        710: "pencil sharpener",
        711: "perfume, essence",
        712: "Petri dish",
        713: "photocopier",
        714: "pick, plectrum, plectron",
        715: "pickelhaube",
        716: "picket fence, paling",
        717: "pickup, pickup truck",
        718: "pier",
        719: "piggy bank, penny bank",
        720: "pill bottle",
        721: "pillow",
        722: "ping-pong ball",
        723: "pinwheel",
        724: "pirate, pirate ship",
        725: "pitcher, ewer",
        726: "plane, carpenter's plane, woodworking plane",
        727: "planetarium",
        728: "plastic bag",
        729: "plate rack",
        730: "plow, plough",
        731: "plunger, plumber's helper",
        732: "Polaroid camera, Polaroid Land camera",
        733: "pole",
        734: "police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria",
        735: "poncho",
        736: "pool table, billiard table, snooker table",
        737: "pop bottle, soda bottle",
        738: "pot, flowerpot",
        739: "potter's wheel",
        740: "power drill",
        741: "prayer rug, prayer mat",
        742: "printer",
        743: "prison, prison house",
        744: "projectile, missile",
        745: "projector",
        746: "puck, hockey puck",
        747: "punching bag, punch bag, punching ball, punchball",
        748: "purse",
        749: "quill, quill pen",
        750: "quilt, comforter, comfort, puff",
        751: "racer, race car, racing car",
        752: "racket, racquet",
        753: "radiator",
        754: "radio, wireless",
        755: "radio telescope, radio reflector",
        756: "rain barrel",
        757: "recreational vehicle, RV, R.V.",
        758: "reel",
        759: "reflex camera",
        760: "refrigerator, icebox",
        761: "remote control, remote",
        762: "restaurant, eating house, eating place, eatery",
        763: "revolver, six-gun, six-shooter",
        764: "rifle",
        765: "rocking chair, rocker",
        766: "rotisserie",
        767: "rubber eraser, rubber, pencil eraser",
        768: "rugby ball",
        769: "rule, ruler",
        770: "running shoe",
        771: "safe",
        772: "safety pin",
        773: "saltshaker, salt shaker",
        774: "sandal",
        775: "sarong",
        776: "sax, saxophone",
        777: "scabbard",
        778: "scale, weighing machine",
        779: "school bus",
        780: "schooner",
        781: "scoreboard",
        782: "screen, CRT screen",
        783: "screw",
        784: "screwdriver",
        785: "seat belt, seatbelt",
        786: "sewing machine",
        787: "shield, buckler",
        788: "shoe shop, shoe-shop, shoe store",
        789: "shoji",
        790: "shopping basket",
        791: "shopping cart",
        792: "shovel",
        793: "shower cap",
        794: "shower curtain",
        795: "ski",
        796: "ski mask",
        797: "sleeping bag",
        798: "slide rule, slipstick",
        799: "sliding door",
        800: "slot, one-armed bandit",
        801: "snorkel",
        802: "snowmobile",
        803: "snowplow, snowplough",
        804: "soap dispenser",
        805: "soccer ball",
        806: "sock",
        807: "solar dish, solar collector, solar furnace",
        808: "sombrero",
        809: "soup bowl",
        810: "space bar",
        811: "space heater",
        812: "space shuttle",
        813: "spatula",
        814: "speedboat",
        815: "spider web, spider's web",
        816: "spindle",
        817: "sports car, sport car",
        818: "spotlight, spot",
        819: "stage",
        820: "steam locomotive",
        821: "steel arch bridge",
        822: "steel drum",
        823: "stethoscope",
        824: "stole",
        825: "stone wall",
        826: "stopwatch, stop watch",
        827: "stove",
        828: "strainer",
        829: "streetcar, tram, tramcar, trolley, trolley car",
        830: "stretcher",
        831: "studio couch, day bed",
        832: "stupa, tope",
        833: "submarine, pigboat, sub, U-boat",
        834: "suit, suit of clothes",
        835: "sundial",
        836: "sunglass",
        837: "sunglasses, dark glasses, shades",
        838: "sunscreen, sunblock, sun blocker",
        839: "suspension bridge",
        840: "swab, swob, mop",
        841: "sweatshirt",
        842: "swimming trunks, bathing trunks",
        843: "swing",
        844: "switch, electric switch, electrical switch",
        845: "syringe",
        846: "table lamp",
        847: "tank, army tank, armored combat vehicle, armoured combat vehicle",
        848: "tape player",
        849: "teapot",
        850: "teddy, teddy bear",
        851: "television, television system",
        852: "tennis ball",
        853: "thatch, thatched roof",
        854: "theater curtain, theatre curtain",
        855: "thimble",
        856: "thresher, thrasher, threshing machine",
        857: "throne",
        858: "tile roof",
        859: "toaster",
        860: "tobacco shop, tobacconist shop, tobacconist",
        861: "toilet seat",
        862: "torch",
        863: "totem pole",
        864: "tow truck, tow car, wrecker",
        865: "toyshop",
        866: "tractor",
        867: "trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi",
        868: "tray",
        869: "trench coat",
        870: "tricycle, trike, velocipede",
        871: "trimaran",
        872: "tripod",
        873: "triumphal arch",
        874: "trolleybus, trolley coach, trackless trolley",
        875: "trombone",
        876: "tub, vat",
        877: "turnstile",
        878: "typewriter keyboard",
        879: "umbrella",
        880: "unicycle, monocycle",
        881: "upright, upright piano",
        882: "vacuum, vacuum cleaner",
        883: "vase",
        884: "vault",
        885: "velvet",
        886: "vending machine",
        887: "vestment",
        888: "viaduct",
        889: "violin, fiddle",
        890: "volleyball",
        891: "waffle iron",
        892: "wall clock",
        893: "wallet, billfold, notecase, pocketbook",
        894: "wardrobe, closet, press",
        895: "warplane, military plane",
        896: "washbasin, handbasin, washbowl, lavabo, wash-hand basin",
        897: "washer, automatic washer, washing machine",
        898: "water bottle",
        899: "water jug",
        900: "water tower",
        901: "whiskey jug",
        902: "whistle",
        903: "wig",
        904: "window screen",
        905: "window shade",
        906: "Windsor tie",
        907: "wine bottle",
        908: "wing",
        909: "wok",
        910: "wooden spoon",
        911: "wool, woolen, woollen",
        912: "worm fence, snake fence, snake-rail fence, Virginia fence",
        913: "wreck",
        914: "yawl",
        915: "yurt",
        916: "web site, website, internet site, site",
        917: "comic book",
        918: "crossword puzzle, crossword",
        919: "street sign",
        920: "traffic light, traffic signal, stoplight",
        921: "book jacket, dust cover, dust jacket, dust wrapper",
        922: "menu",
        923: "plate",
        924: "guacamole",
        925: "consomme",
        926: "hot pot, hotpot",
        927: "trifle",
        928: "ice cream, icecream",
        929: "ice lolly, lolly, lollipop, popsicle",
        930: "French loaf",
        931: "bagel, beigel",
        932: "pretzel",
        933: "cheeseburger",
        934: "hotdog, hot dog, red hot",
        935: "mashed potato",
        936: "head cabbage",
        937: "broccoli",
        938: "cauliflower",
        939: "zucchini, courgette",
        940: "spaghetti squash",
        941: "acorn squash",
        942: "butternut squash",
        943: "cucumber, cuke",
        944: "artichoke, globe artichoke",
        945: "bell pepper",
        946: "cardoon",
        947: "mushroom",
        948: "Granny Smith",
        949: "strawberry",
        950: "orange",
        951: "lemon",
        952: "fig",
        953: "pineapple, ananas",
        954: "banana",
        955: "jackfruit, jak, jack",
        956: "custard apple",
        957: "pomegranate",
        958: "hay",
        959: "carbonara",
        960: "chocolate sauce, chocolate syrup",
        961: "dough",
        962: "meat loaf, meatloaf",
        963: "pizza, pizza pie",
        964: "potpie",
        965: "burrito",
        966: "red wine",
        967: "espresso",
        968: "cup",
        969: "eggnog",
        970: "alp",
        971: "bubble",
        972: "cliff, drop, drop-off",
        973: "coral reef",
        974: "geyser",
        975: "lakeside, lakeshore",
        976: "promontory, headland, head, foreland",
        977: "sandbar, sand bar",
        978: "seashore, coast, seacoast, sea-coast",
        979: "valley, vale",
        980: "volcano",
        981: "ballplayer, baseball player",
        982: "groom, bridegroom",
        983: "scuba diver",
        984: "rapeseed",
        985: "daisy",
        986: "yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum",
        987: "corn",
        988: "acorn",
        989: "hip, rose hip, rosehip",
        990: "buckeye, horse chestnut, conker",
        991: "coral fungus",
        992: "agaric",
        993: "gyromitra",
        994: "stinkhorn, carrion fungus",
        995: "earthstar",
        996: "hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa",
        997: "bolete",
        998: "ear, spike, capitulum",
        999: "toilet tissue, toilet paper, bathroom tissue",
    }

    return class_label_dict
