import glob
import os
import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np
import matplotlib.image as mpimg
import pandas as pd
import cv2


class FacialKeypointsDataset(Dataset):
    """Face Landmarks dataset."""

    def __init__(self, csv_file, root_dir, transform=None):
        """
        Args:
            csv_file (string): Path to the csv file with annotations.
            root_dir (string): Directory with all the images.
            transform (callable, optional): Optional transform to be applied
                on a sample.
        """
        self.key_pts_frame = pd.read_csv(csv_file)
        self.root_dir = root_dir
        self.transform = transform

    def __len__(self):
        return len(self.key_pts_frame)

    def __getitem__(self, idx):
        image_name = os.path.join(self.root_dir,
                                self.key_pts_frame.iloc[idx, 0])
        
        image = mpimg.imread(image_name)
        
        # if image has an alpha color channel, get rid of it
        if(image.shape[2] == 4):
            image = image[:,:,0:3]
        
        key_pts = self.key_pts_frame.iloc[idx, 1:].values
        key_pts = key_pts.astype('float').reshape(-1, 2)
        sample = {'image': image, 'keypoints': key_pts}

        if self.transform:
            sample = self.transform(sample)

        return sample
    

# tranforms


class Normalize(object):
    """Convert a color image to grayscale and normalize the color range to [0,1]."""        

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']
        
        image_copy = np.copy(image)
        key_pts_copy = np.copy(key_pts)

        # convert image to grayscale
        image_copy = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        
        # scale color range from [0, 255] to [0, 1]
        image_copy = image_copy/255.0
            
        
        # scale keypoints to be centered around 0 with a range of [-1, 1]
        # mean = 100, sqrt = 50, so, pts should be (pts - 100)/50
        key_pts_copy = (key_pts_copy - 100)/50.0

        return {'image': image_copy, 'keypoints': key_pts_copy}


class Rescale(object):
    """Rescale the image in a sample to a given size.

    Args:
        output_size (tuple or int): Desired output size. If tuple, output is
            matched to output_size. If int, smaller of image edges is matched
            to output_size keeping aspect ratio the same.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        self.output_size = output_size

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        h, w = image.shape[:2]
        if isinstance(self.output_size, int):
            if h > w:
                new_h, new_w = self.output_size * h / w, self.output_size
            else:
                new_h, new_w = self.output_size, self.output_size * w / h
        else:
            new_h, new_w = self.output_size

        new_h, new_w = int(new_h), int(new_w)

        img = cv2.resize(image, (new_w, new_h))
        
        # scale the pts, too
        key_pts = key_pts * [new_w / w, new_h / h]

        return {'image': img, 'keypoints': key_pts}


class RandomCrop(object):
    """Crop randomly the image in a sample.

    Args:
        output_size (tuple or int): Desired output size. If int, square crop
            is made.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        h, w = image.shape[:2]
        new_h, new_w = self.output_size

        top = np.random.randint(0, h - new_h)
        left = np.random.randint(0, w - new_w)

        image = image[top: top + new_h,
                      left: left + new_w]

        key_pts = key_pts - [left, top]

        return {'image': image, 'keypoints': key_pts}


class RandomFlip(object):

    def __init__(self, always_flip=False):
        self.always_flip = always_flip
        left = list(range(1, 9)) + list(range(37, 43)) + list(range(18, 23)) + [32, 33] + \
               [51, 50, 49, 60, 59, 61, 62, 68]
        right = list(range(17, 9, -1)) + [46, 45, 44, 43, 48, 47] + list(range(27, 22, -1)) + [36, 35] + \
                list(range(53, 58)) + [65, 64, 66]
        center = list(range(28, 32)) + [34, 52, 63, 67, 58, 9]

        left = [l - 1 for l in left]
        right = [l - 1 for l in right]
        center = [l - 1 for l in center]

        self.new_perm = np.arange(68)
        self.new_perm[left] = right
        self.new_perm[right] = left

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        to_flip = np.random.randint(low=0, high=2)
        if self.always_flip or (to_flip == 1):
            image = cv2.flip(image, 1)
            key_pts = key_pts[self.new_perm, :]
            for i in range(len(key_pts)):
                key_pts[i, 0] = image.shape[1] - key_pts[i, 0]

        return {'image': image, 'keypoints': key_pts}


class RandomRotate(object):

    @staticmethod
    def get_rotation_mat(image, angle):
        image_center = tuple(np.array(image.shape[1::-1]) / 2)
        rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
        return rot_mat

    @staticmethod
    def rotate_image(image, rot_mat):
        result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
        return result

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        angle = np.random.uniform(low=-10.0, high=10.0)
        rot_mat = self.get_rotation_mat(image, angle)

        image = self.rotate_image(image, rot_mat)
        for i in range(len(key_pts)):
            key_pts[i] = np.dot(rot_mat, np.concatenate((key_pts[i], [1])))

        return {'image': image, 'keypoints': key_pts}


class ToTensor(object):
    """Convert ndarrays in sample to Tensors."""

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']
         
        # if image has no grayscale color channel, add one
        if(len(image.shape) == 2):
            # add that third color dim
            image = image.reshape(image.shape[0], image.shape[1], 1)
            
        # swap color axis because
        # numpy image: H x W x C
        # torch image: C X H X W
        image = image.transpose((2, 0, 1))
        
        return {'image': torch.from_numpy(image),
                'keypoints': torch.from_numpy(key_pts)}
import glob
import os
import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np
import matplotlib.image as mpimg
import pandas as pd
import cv2


class FacialKeypointsDataset(Dataset):
    """Face Landmarks dataset."""

    def __init__(self, csv_file, root_dir, transform=None):
        """
        Args:
            csv_file (string): Path to the csv file with annotations.
            root_dir (string): Directory with all the images.
            transform (callable, optional): Optional transform to be applied
                on a sample.
        """
        self.key_pts_frame = pd.read_csv(csv_file)
        self.root_dir = root_dir
        self.transform = transform

    def __len__(self):
        return len(self.key_pts_frame)

    def __getitem__(self, idx):
        image_name = os.path.join(self.root_dir,
                                self.key_pts_frame.iloc[idx, 0])
        
        image = mpimg.imread(image_name)
        
        # if image has an alpha color channel, get rid of it
        if(image.shape[2] == 4):
            image = image[:,:,0:3]
        
        key_pts = self.key_pts_frame.iloc[idx, 1:].values
        key_pts = key_pts.astype('float').reshape(-1, 2)
        sample = {'image': image, 'keypoints': key_pts}

        if self.transform:
            sample = self.transform(sample)

        return sample
    

# tranforms


class Normalize(object):
    """Convert a color image to grayscale and normalize the color range to [0,1]."""        

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']
        
        image_copy = np.copy(image)
        key_pts_copy = np.copy(key_pts)

        # convert image to grayscale
        image_copy = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        
        # scale color range from [0, 255] to [0, 1]
        image_copy = image_copy/255.0
            
        
        # scale keypoints to be centered around 0 with a range of [-1, 1]
        # mean = 100, sqrt = 50, so, pts should be (pts - 100)/50
        key_pts_copy = (key_pts_copy - 100)/50.0

        return {'image': image_copy, 'keypoints': key_pts_copy}


class Rescale(object):
    """Rescale the image in a sample to a given size.

    Args:
        output_size (tuple or int): Desired output size. If tuple, output is
            matched to output_size. If int, smaller of image edges is matched
            to output_size keeping aspect ratio the same.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        self.output_size = output_size

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        h, w = image.shape[:2]
        if isinstance(self.output_size, int):
            if h > w:
                new_h, new_w = self.output_size * h / w, self.output_size
            else:
                new_h, new_w = self.output_size, self.output_size * w / h
        else:
            new_h, new_w = self.output_size

        new_h, new_w = int(new_h), int(new_w)

        img = cv2.resize(image, (new_w, new_h))
        
        # scale the pts, too
        key_pts = key_pts * [new_w / w, new_h / h]

        return {'image': img, 'keypoints': key_pts}


class RandomCrop(object):
    """Crop randomly the image in a sample.

    Args:
        output_size (tuple or int): Desired output size. If int, square crop
            is made.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        h, w = image.shape[:2]
        new_h, new_w = self.output_size

        top = np.random.randint(0, h - new_h)
        left = np.random.randint(0, w - new_w)

        image = image[top: top + new_h,
                      left: left + new_w]

        key_pts = key_pts - [left, top]

        return {'image': image, 'keypoints': key_pts}


class RandomFlip(object):

    def __init__(self, always_flip=False):
        self.always_flip = always_flip
        left = list(range(1, 9)) + list(range(37, 43)) + list(range(18, 23)) + [32, 33] + \
               [51, 50, 49, 60, 59, 61, 62, 68]
        right = list(range(17, 9, -1)) + [46, 45, 44, 43, 48, 47] + list(range(27, 22, -1)) + [36, 35] + \
                list(range(53, 58)) + [65, 64, 66]
        center = list(range(28, 32)) + [34, 52, 63, 67, 58, 9]

        left = [l - 1 for l in left]
        right = [l - 1 for l in right]
        center = [l - 1 for l in center]

        self.new_perm = np.arange(68)
        self.new_perm[left] = right
        self.new_perm[right] = left

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        to_flip = np.random.randint(low=0, high=2)
        if self.always_flip or (to_flip == 1):
            image = cv2.flip(image, 1)
            key_pts = key_pts[self.new_perm, :]
            for i in range(len(key_pts)):
                key_pts[i, 0] = image.shape[1] - key_pts[i, 0]

        return {'image': image, 'keypoints': key_pts}


class RandomRotate(object):

    @staticmethod
    def get_rotation_mat(image, angle):
        image_center = tuple(np.array(image.shape[1::-1]) / 2)
        rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
        return rot_mat

    @staticmethod
    def rotate_image(image, rot_mat):
        result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
        return result

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']

        angle = np.random.uniform(low=-10.0, high=10.0)
        rot_mat = self.get_rotation_mat(image, angle)

        image = self.rotate_image(image, rot_mat)
        for i in range(len(key_pts)):
            key_pts[i] = np.dot(rot_mat, np.concatenate((key_pts[i], [1])))

        return {'image': image, 'keypoints': key_pts}


class ToTensor(object):
    """Convert ndarrays in sample to Tensors."""

    def __call__(self, sample):
        image, key_pts = sample['image'], sample['keypoints']
         
        # if image has no grayscale color channel, add one
        if(len(image.shape) == 2):
            # add that third color dim
            image = image.reshape(image.shape[0], image.shape[1], 1)
            
        # swap color axis because
        # numpy image: H x W x C
        # torch image: C X H X W
        image = image.transpose((2, 0, 1))
        
        return {'image': torch.from_numpy(image),
                'keypoints': torch.from_numpy(key_pts)}
Face Landmarks dataset. Args:             csv_file (string): Path to the csv file with annotations.             root_dir (string): Directory with all the images.             transform (callable, optional): Optional transform to be applied                 on a sample. # if image has an alpha color channel, get rid of it # tranforms Convert a color image to grayscale and normalize the color range to [0,1]. # convert image to grayscale # scale color range from [0, 255] to [0, 1] # scale keypoints to be centered around 0 with a range of [-1, 1] # mean = 100, sqrt = 50, so, pts should be (pts - 100)/50 Rescale the image in a sample to a given size.      Args:         output_size (tuple or int): Desired output size. If tuple, output is             matched to output_size. If int, smaller of image edges is matched             to output_size keeping aspect ratio the same. # scale the pts, too Crop randomly the image in a sample.      Args:         output_size (tuple or int): Desired output size. If int, square crop             is made. Convert ndarrays in sample to Tensors. # if image has no grayscale color channel, add one # add that third color dim # swap color axis because # numpy image: H x W x C # torch image: C X H X W