Module narya.datasets.homography_dataset

Expand source code
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import mxnet as mx
import os
import cv2
import keras
import numpy as np
import keras.backend as K
from lxml import etree
import six
import albumentations as A
import tensorflow as tf

from albumentations import DualTransform

from ..utils.homography import (
    vertical_flip_homo,
    horizontal_flip_homo,
    get_four_corners,
    normalize_homo,
)
from ..preprocessing.image import _build_homo_preprocessing


def new_tf_targets(self):
    """Adds a new object to Albumentations. Allows it to deal not only with:
        * Images
        * Boxes
        * Masks
        * Keypoints
    but also with custom object. In this case: homography.
    We will only need to define custom function ```python3 self.apply_to_object``` for each
    albumentations object we use.
    """
    return {
        "image": self.apply,
        "homo": self.apply_to_homo,
        "mask": self.apply_to_mask,
        "bboxes": self.apply_to_bboxes,
    }


DualTransform.targets = property(new_tf_targets)


class HorizontalFlipWithHomo(A.HorizontalFlip):
    """Class based of albumentations.HorizontalFlip, to allow it to deal with homographies.

    """

    def apply_to_homo(self, homo, **params):
        return horizontal_flip_homo(homo, **params)


class Lambda(A.Lambda):
    """Class based of albumentations.Lambda, to allow it to deal with homographies.

    """

    def apply_to_homo(self, homo, **params):
        return homo


class RandomCropWithHomo(A.RandomCrop):
    """Class based of albumentations.RandomCrop, to allow it to deal with homographies.

    """

    def apply_to_homo(self, homo, **params):
        return homo


class Dataset:
    """Class for an homography dataset. Allows to load pairs of image, homography, and
    apply random transformation to them.

    Arguments:
        images_dir: Path to the folder containing the images, in a '.jpg' format.
        homo_dir: Path to the folder containing the homographies, in a '.npy' format.
                    The homography must have the same name as the image they are linked to.
        augmentation: None if we don't apply random transformation. Else, a function to apply.
        preprocessing: None if we don't apply random preprocessing. Else, a function to apply.
    
    """

    def __init__(self, images_dir, homo_dir, augmentation=None, preprocessing=None):
        self.ids = os.listdir(images_dir)
        self.images_fps = [os.path.join(images_dir, image_id) for image_id in self.ids]
        self.homo_fps = [
            os.path.join(homo_dir, image_id.replace(".jpg", "_homo.npy"))
            for image_id in self.ids
        ]

        self.augmentation = augmentation
        self.preprocessing = preprocessing

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

    def __getitem__(self, i):

        # read data
        image = cv2.imread(self.images_fps[i])
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        image = cv2.resize(image, (280, 280))
        homo = np.load(self.homo_fps[i])
        homo = homo[0] if len(homo.shape) > 2 else homo

        # apply augmentations
        temp_homo_0 = homo[0][0]
        if self.augmentation:
            sample = self.augmentation(image=image, homo=homo)
            image, homo = sample["image"], sample["homo"]

        # apply preprocessing
        if self.preprocessing:
            sample = self.preprocessing(image=image)
            image = sample["image"]

        homo = normalize_homo(homo)

        if temp_homo_0 != homo[0][0]:
            homo = get_four_corners(homo)[0]
            for i in range(4):
                homo[0][i] = -homo[0][i]
        else:
            homo = get_four_corners(homo)[0]
        return image, homo.flatten()


def get_training_augmentation():
    """Builds random transformations we want to apply to our dataset.

    Arguments:
        
    Returns:
        A albumentation functions to pass our images to.
    Raises:

    """
    train_transform = [
        HorizontalFlipWithHomo(p=0.5),
        A.IAAAdditiveGaussianNoise(p=0.2),
        A.augmentations.transforms.RandomShadow(
            shadow_roi=(0, 0.5, 1, 1),
            num_shadows_lower=1,
            num_shadows_upper=1,
            shadow_dimension=3,
            always_apply=False,
            p=0.5,
        ),
        A.OneOf([A.RandomBrightness(p=1),], p=0.3,),
        A.OneOf([A.Blur(blur_limit=3, p=1), A.MotionBlur(blur_limit=3, p=1),], p=0.3,),
        A.OneOf([A.RandomContrast(p=1), A.HueSaturationValue(p=1),], p=0.3,),
        RandomCropWithHomo(height=256, width=256, always_apply=True),
    ]
    return A.Compose(train_transform)


def get_validation_augmentation():
    """Builds random transformations we want to apply to our dataset.

    Arguments:
        
    Returns:
        A albumentation functions to pass our images to.
    Raises:

    """
    train_transform = [
        HorizontalFlipWithHomo(p=0.5),
        RandomCropWithHomo(height=256, width=256, always_apply=True),
    ]
    return A.Compose(train_transform)


def get_preprocessing(preprocessing_fn):
    """Construct preprocessing transform
    
    Arguments:
        preprocessing_fn (callable): data normalization function 
            (can be specific for each pretrained neural network)
    Return:
        transform: albumentations.Compose
    Raises:
    """

    _transform = [
        Lambda(name="homo_preprocessing", image=preprocessing_fn),
    ]
    return A.Compose(_transform)


class Dataloder(tf.keras.utils.Sequence):
    """Load data from dataset and form batches
    
    Arguments:
        dataset: instance of Dataset class for image loading and preprocessing.
        batch_size: Integer number of images in batch.
        shuffle: Boolean, if `True` shuffle image indexes each epoch.
    """

    def __init__(self, dataset, batch_size=1, shuffle=False):
        self.dataset = dataset
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.indexes = np.arange(len(dataset))

        self.on_epoch_end()

    def __getitem__(self, i):

        images = []
        homos = []
        # collect batch data
        start = i * self.batch_size
        stop = (i + 1) * self.batch_size
        data = []
        for j in range(start, stop):
            x_y = self.dataset[j]
            image = x_y[0]
            true_homo = x_y[1]
            images.append(image)
            homos.append(true_homo)

        batch_image = np.array(images)
        batch_true_homo = np.array(homos)

        return (batch_image, batch_true_homo)

    def __len__(self):
        """Denotes the number of batches per epoch"""
        return len(self.indexes) // self.batch_size

    def on_epoch_end(self):
        """Callback function to shuffle indexes each epoch"""
        if self.shuffle:
            self.indexes = np.random.permutation(self.indexes)


class HomographyDatasetBuilder:
    """Class for an homography dataset. Allows to load pairs of image, homography, and
    apply random transformation to them. Also loads the dataloader you can then pass to a keras model.

    Arguments:
        img_train_dir: Path to the folder containing the training images, in a '.jpg' format.
        img_test_dir: Path to the folder containing the testing images, in a '.jpg' format.
        homo_train_dir: Path to the folder containing the training homographies, in a '.npy' format.
                    The homography must have the same name as the image they are linked to.
        homo_test_dir: Path to the folder containing the testing homographies, in a '.npy' format.
                    The homography must have the same name as the image they are linked to.
        batch_size: Integer number of images in batch.
        preprocess_input: None, or a preprocessing function to apply on the images.
        shuffle: Boolean, if `True` shuffle image indexes each epoch.
    """

    def __init__(
        self,
        img_train_dir,
        img_test_dir,
        homo_train_dir,
        homo_test_dir,
        batch_size,
        preprocess_input,
        shuffle=True,
    ):

        self.train_dataset = Dataset(
            img_train_dir,
            homo_train_dir,
            augmentation=get_training_augmentation(),
            preprocessing=get_preprocessing(preprocess_input),
        )

        self.valid_dataset = Dataset(
            img_test_dir,
            homo_test_dir,
            augmentation=get_validation_augmentation(),
            preprocessing=get_preprocessing(preprocess_input),
        )

        self.train_dataloader = Dataloder(
            self.train_dataset, batch_size=batch_size, shuffle=shuffle
        )
        self.valid_dataloader = Dataloder(
            self.valid_dataset, batch_size=1, shuffle=False
        )

    def _get_dataset(self):
        return self.train_dataset, self.valid_dataset

    def _get_dataloader(self):
        return self.train_dataloader, self.valid_dataloader

Functions

def get_preprocessing(preprocessing_fn)

Construct preprocessing transform

Arguments

preprocessing_fn (callable): data normalization function (can be specific for each pretrained neural network)

Return

transform: albumentations.Compose Raises:

Expand source code
def get_preprocessing(preprocessing_fn):
    """Construct preprocessing transform
    
    Arguments:
        preprocessing_fn (callable): data normalization function 
            (can be specific for each pretrained neural network)
    Return:
        transform: albumentations.Compose
    Raises:
    """

    _transform = [
        Lambda(name="homo_preprocessing", image=preprocessing_fn),
    ]
    return A.Compose(_transform)
def get_training_augmentation()

Builds random transformations we want to apply to our dataset.

Arguments

Returns

A albumentation functions to pass our images to. Raises:

Expand source code
def get_training_augmentation():
    """Builds random transformations we want to apply to our dataset.

    Arguments:
        
    Returns:
        A albumentation functions to pass our images to.
    Raises:

    """
    train_transform = [
        HorizontalFlipWithHomo(p=0.5),
        A.IAAAdditiveGaussianNoise(p=0.2),
        A.augmentations.transforms.RandomShadow(
            shadow_roi=(0, 0.5, 1, 1),
            num_shadows_lower=1,
            num_shadows_upper=1,
            shadow_dimension=3,
            always_apply=False,
            p=0.5,
        ),
        A.OneOf([A.RandomBrightness(p=1),], p=0.3,),
        A.OneOf([A.Blur(blur_limit=3, p=1), A.MotionBlur(blur_limit=3, p=1),], p=0.3,),
        A.OneOf([A.RandomContrast(p=1), A.HueSaturationValue(p=1),], p=0.3,),
        RandomCropWithHomo(height=256, width=256, always_apply=True),
    ]
    return A.Compose(train_transform)
def get_validation_augmentation()

Builds random transformations we want to apply to our dataset.

Arguments

Returns

A albumentation functions to pass our images to. Raises:

Expand source code
def get_validation_augmentation():
    """Builds random transformations we want to apply to our dataset.

    Arguments:
        
    Returns:
        A albumentation functions to pass our images to.
    Raises:

    """
    train_transform = [
        HorizontalFlipWithHomo(p=0.5),
        RandomCropWithHomo(height=256, width=256, always_apply=True),
    ]
    return A.Compose(train_transform)
def new_tf_targets(self)

Adds a new object to Albumentations. Allows it to deal not only with: * Images * Boxes * Masks * Keypoints but also with custom object. In this case: homography. We will only need to define custom function python3 self.apply_to_object for each albumentations object we use.

Expand source code
def new_tf_targets(self):
    """Adds a new object to Albumentations. Allows it to deal not only with:
        * Images
        * Boxes
        * Masks
        * Keypoints
    but also with custom object. In this case: homography.
    We will only need to define custom function ```python3 self.apply_to_object``` for each
    albumentations object we use.
    """
    return {
        "image": self.apply,
        "homo": self.apply_to_homo,
        "mask": self.apply_to_mask,
        "bboxes": self.apply_to_bboxes,
    }

Classes

class Dataloder (dataset, batch_size=1, shuffle=False)

Load data from dataset and form batches

Arguments

dataset: instance of Dataset class for image loading and preprocessing. batch_size: Integer number of images in batch. shuffle: Boolean, if True shuffle image indexes each epoch.

Expand source code
class Dataloder(tf.keras.utils.Sequence):
    """Load data from dataset and form batches
    
    Arguments:
        dataset: instance of Dataset class for image loading and preprocessing.
        batch_size: Integer number of images in batch.
        shuffle: Boolean, if `True` shuffle image indexes each epoch.
    """

    def __init__(self, dataset, batch_size=1, shuffle=False):
        self.dataset = dataset
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.indexes = np.arange(len(dataset))

        self.on_epoch_end()

    def __getitem__(self, i):

        images = []
        homos = []
        # collect batch data
        start = i * self.batch_size
        stop = (i + 1) * self.batch_size
        data = []
        for j in range(start, stop):
            x_y = self.dataset[j]
            image = x_y[0]
            true_homo = x_y[1]
            images.append(image)
            homos.append(true_homo)

        batch_image = np.array(images)
        batch_true_homo = np.array(homos)

        return (batch_image, batch_true_homo)

    def __len__(self):
        """Denotes the number of batches per epoch"""
        return len(self.indexes) // self.batch_size

    def on_epoch_end(self):
        """Callback function to shuffle indexes each epoch"""
        if self.shuffle:
            self.indexes = np.random.permutation(self.indexes)

Ancestors

  • tensorflow.python.keras.utils.data_utils.Sequence

Methods

def on_epoch_end(self)

Callback function to shuffle indexes each epoch

Expand source code
def on_epoch_end(self):
    """Callback function to shuffle indexes each epoch"""
    if self.shuffle:
        self.indexes = np.random.permutation(self.indexes)
class Dataset (images_dir, homo_dir, augmentation=None, preprocessing=None)

Class for an homography dataset. Allows to load pairs of image, homography, and apply random transformation to them.

Arguments

images_dir: Path to the folder containing the images, in a '.jpg' format. homo_dir: Path to the folder containing the homographies, in a '.npy' format. The homography must have the same name as the image they are linked to. augmentation: None if we don't apply random transformation. Else, a function to apply. preprocessing: None if we don't apply random preprocessing. Else, a function to apply.

Expand source code
class Dataset:
    """Class for an homography dataset. Allows to load pairs of image, homography, and
    apply random transformation to them.

    Arguments:
        images_dir: Path to the folder containing the images, in a '.jpg' format.
        homo_dir: Path to the folder containing the homographies, in a '.npy' format.
                    The homography must have the same name as the image they are linked to.
        augmentation: None if we don't apply random transformation. Else, a function to apply.
        preprocessing: None if we don't apply random preprocessing. Else, a function to apply.
    
    """

    def __init__(self, images_dir, homo_dir, augmentation=None, preprocessing=None):
        self.ids = os.listdir(images_dir)
        self.images_fps = [os.path.join(images_dir, image_id) for image_id in self.ids]
        self.homo_fps = [
            os.path.join(homo_dir, image_id.replace(".jpg", "_homo.npy"))
            for image_id in self.ids
        ]

        self.augmentation = augmentation
        self.preprocessing = preprocessing

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

    def __getitem__(self, i):

        # read data
        image = cv2.imread(self.images_fps[i])
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        image = cv2.resize(image, (280, 280))
        homo = np.load(self.homo_fps[i])
        homo = homo[0] if len(homo.shape) > 2 else homo

        # apply augmentations
        temp_homo_0 = homo[0][0]
        if self.augmentation:
            sample = self.augmentation(image=image, homo=homo)
            image, homo = sample["image"], sample["homo"]

        # apply preprocessing
        if self.preprocessing:
            sample = self.preprocessing(image=image)
            image = sample["image"]

        homo = normalize_homo(homo)

        if temp_homo_0 != homo[0][0]:
            homo = get_four_corners(homo)[0]
            for i in range(4):
                homo[0][i] = -homo[0][i]
        else:
            homo = get_four_corners(homo)[0]
        return image, homo.flatten()
class HomographyDatasetBuilder (img_train_dir, img_test_dir, homo_train_dir, homo_test_dir, batch_size, preprocess_input, shuffle=True)

Class for an homography dataset. Allows to load pairs of image, homography, and apply random transformation to them. Also loads the dataloader you can then pass to a keras model.

Arguments

img_train_dir: Path to the folder containing the training images, in a '.jpg' format. img_test_dir: Path to the folder containing the testing images, in a '.jpg' format. homo_train_dir: Path to the folder containing the training homographies, in a '.npy' format. The homography must have the same name as the image they are linked to. homo_test_dir: Path to the folder containing the testing homographies, in a '.npy' format. The homography must have the same name as the image they are linked to. batch_size: Integer number of images in batch. preprocess_input: None, or a preprocessing function to apply on the images. shuffle: Boolean, if True shuffle image indexes each epoch.

Expand source code
class HomographyDatasetBuilder:
    """Class for an homography dataset. Allows to load pairs of image, homography, and
    apply random transformation to them. Also loads the dataloader you can then pass to a keras model.

    Arguments:
        img_train_dir: Path to the folder containing the training images, in a '.jpg' format.
        img_test_dir: Path to the folder containing the testing images, in a '.jpg' format.
        homo_train_dir: Path to the folder containing the training homographies, in a '.npy' format.
                    The homography must have the same name as the image they are linked to.
        homo_test_dir: Path to the folder containing the testing homographies, in a '.npy' format.
                    The homography must have the same name as the image they are linked to.
        batch_size: Integer number of images in batch.
        preprocess_input: None, or a preprocessing function to apply on the images.
        shuffle: Boolean, if `True` shuffle image indexes each epoch.
    """

    def __init__(
        self,
        img_train_dir,
        img_test_dir,
        homo_train_dir,
        homo_test_dir,
        batch_size,
        preprocess_input,
        shuffle=True,
    ):

        self.train_dataset = Dataset(
            img_train_dir,
            homo_train_dir,
            augmentation=get_training_augmentation(),
            preprocessing=get_preprocessing(preprocess_input),
        )

        self.valid_dataset = Dataset(
            img_test_dir,
            homo_test_dir,
            augmentation=get_validation_augmentation(),
            preprocessing=get_preprocessing(preprocess_input),
        )

        self.train_dataloader = Dataloder(
            self.train_dataset, batch_size=batch_size, shuffle=shuffle
        )
        self.valid_dataloader = Dataloder(
            self.valid_dataset, batch_size=1, shuffle=False
        )

    def _get_dataset(self):
        return self.train_dataset, self.valid_dataset

    def _get_dataloader(self):
        return self.train_dataloader, self.valid_dataloader
class HorizontalFlipWithHomo (always_apply=False, p=0.5)

Class based of albumentations.HorizontalFlip, to allow it to deal with homographies.

Expand source code
class HorizontalFlipWithHomo(A.HorizontalFlip):
    """Class based of albumentations.HorizontalFlip, to allow it to deal with homographies.

    """

    def apply_to_homo(self, homo, **params):
        return horizontal_flip_homo(homo, **params)

Ancestors

  • albumentations.augmentations.transforms.HorizontalFlip
  • albumentations.core.transforms_interface.DualTransform
  • albumentations.core.transforms_interface.BasicTransform

Methods

def apply_to_homo(self, homo, **params)
Expand source code
def apply_to_homo(self, homo, **params):
    return horizontal_flip_homo(homo, **params)
class Lambda (image=None, mask=None, keypoint=None, bbox=None, name=None, always_apply=False, p=1.0)

Class based of albumentations.Lambda, to allow it to deal with homographies.

Expand source code
class Lambda(A.Lambda):
    """Class based of albumentations.Lambda, to allow it to deal with homographies.

    """

    def apply_to_homo(self, homo, **params):
        return homo

Ancestors

  • albumentations.augmentations.transforms.Lambda
  • albumentations.core.transforms_interface.NoOp
  • albumentations.core.transforms_interface.DualTransform
  • albumentations.core.transforms_interface.BasicTransform

Methods

def apply_to_homo(self, homo, **params)
Expand source code
def apply_to_homo(self, homo, **params):
    return homo
class RandomCropWithHomo (height, width, always_apply=False, p=1.0)

Class based of albumentations.RandomCrop, to allow it to deal with homographies.

Expand source code
class RandomCropWithHomo(A.RandomCrop):
    """Class based of albumentations.RandomCrop, to allow it to deal with homographies.

    """

    def apply_to_homo(self, homo, **params):
        return homo

Ancestors

  • albumentations.augmentations.transforms.RandomCrop
  • albumentations.core.transforms_interface.DualTransform
  • albumentations.core.transforms_interface.BasicTransform

Methods

def apply_to_homo(self, homo, **params)
Expand source code
def apply_to_homo(self, homo, **params):
    return homo