  
          logger.trace("Target image shapes: %s", [tgt.shape for tgt in target_images])
          return self.compile_images(warped_image, target_images)
  
      def random_warp_landmarks(self, image, src_points=None, dst_points=None):
          """ get warped image, target image and target mask
              From DFAKER plugin """
          logger.trace("Randomly warping landmarks")
          size = image.shape[0]
          coverage = self.get_coverage(image) // 2
  
          p_mx = size - 1
          p_hf = (size // 2) - 1
  
          edge_anchors = [(0, 0), (0, p_mx), (p_mx, p_mx), (p_mx, 0),
                          (p_hf, 0), (p_hf, p_mx), (p_mx, p_hf), (0, p_hf)]
          grid_x, grid_y = np.mgrid[0:p_mx:complex(size), 0:p_mx:complex(size)]
  
          source = src_points
          destination = (dst_points.copy().astype('float32') +
                         np.random.normal(size=dst_points.shape, scale=2.0))
          destination = destination.astype('uint8')
  
          face_core = cv2.convexHull(np.concatenate(  # pylint:disable=no-member
              [source[17:], destination[17:]], axis=0).astype(int))
  
          source = [(pty, ptx) for ptx, pty in source] + edge_anchors
          destination = [(pty, ptx) for ptx, pty in destination] + edge_anchors
  
          indicies_to_remove = set()
          for fpl in source, destination:
              for idx, (pty, ptx) in enumerate(fpl):
                  if idx > 17:
                      break
                  elif cv2.pointPolygonTest(face_core,  # pylint:disable=no-member
                                            (pty, ptx),
                                            False) >= 0:
                      indicies_to_remove.add(idx)
  
          for idx in sorted(indicies_to_remove, reverse=True):
              source.pop(idx)
              destination.pop(idx)
  
          grid_z = griddata(destination, source, (grid_x, grid_y), method="linear")
          map_x = np.append([], [ar[:, 1] for ar in grid_z]).reshape(size, size)
          map_y = np.append([], [ar[:, 0] for ar in grid_z]).reshape(size, size)
          map_x_32 = map_x.astype('float32')
          map_y_32 = map_y.astype('float32')
  
          warped_image = cv2.remap(image,  # pylint:disable=no-member
                                   map_x_32,
                                   map_y_32,
                                   cv2.INTER_LINEAR,  # pylint:disable=no-member
                                   cv2.BORDER_TRANSPARENT)  # pylint:disable=no-member
          target_image = image
  
          # TODO Make sure this replacement is correct
          slices = slice(size // 2 - coverage, size // 2 + coverage)
  #        slices = slice(size // 32, size - size // 32)  # 8px on a 256px image
          warped_image = cv2.resize(  # pylint:disable=no-member
              warped_image[slices, slices, :], (self.input_size, self.input_size),
              cv2.INTER_AREA)  # pylint:disable=no-member
          logger.trace("Warped image shape: %s", warped_image.shape)
          target_images = [cv2.resize(target_image[slices, slices, :],  # pylint:disable=no-member
                                      (size, size),
                                      cv2.INTER_AREA)  # pylint:disable=no-member
                           for size in self.output_sizes]
  
          logger.trace("Target image shapea: %s", [img.shape for img in target_images])
          return self.compile_images(warped_image, target_images)
  
      def compile_images(self, warped_image, target_images):
          """ Compile the warped images, target images and mask for feed """
          warped_image, _ = self.separate_mask(warped_image)
          final_target_images = list()
          target_mask = None
          for target_image in target_images:
              image, mask = self.separate_mask(target_image)
              final_target_images.append(image)
              # Add the mask if it exists and is the same size as our largest output
              if mask is not None and mask.shape[1] == max(self.output_sizes):
                  target_mask = mask
  
          retval = [warped_image] + final_target_images
          if target_mask is not None:
              logger.trace("Target mask shape: %s", target_mask.shape)
              retval.append(target_mask)
  
          logger.trace("Final shapes: %s", [img.shape for img in retval])
          return retval
  
  
  def stack_images(images):
      """ Stack images """
      logger.debug("Stack images")
 
</s> remove         logger.trace("Random LAB adjustments: %s", randoms)
        image = cv2.cvtColor(  # pylint:disable=no-member
            image, cv2.COLOR_BGR2LAB).astype("float32") / 255.0  # pylint:disable=no-member

        for idx, adjustment in enumerate(randoms):
            if adjustment >= 0:
                image[:, :, idx] = ((1 - image[:, :, idx]) * adjustment) + image[:, :, idx]
            else:
                image[:, :, idx] = image[:, :, idx] * (1 + adjustment)
        image = cv2.cvtColor((image * 255.0).astype("uint8"),  # pylint:disable=no-member
                             cv2.COLOR_LAB2BGR)  # pylint:disable=no-member
        return image
 </s> add     def initialize(self, training_size):
        """ Initializes the caching of constants for use in various image augmentations.

        The training image size is not known prior to loading the images from disk and commencing
        training, so it cannot be set in the ``__init__`` method. When the first training batch is
        loaded this function should be called to initialize the class and perform various
        calculations based on this input size to cache certain constants for image augmentation
        calculations.

        Parameters
        ----------
        training_size: int
             The size of the training images stored on disk that are to be fed into
             :class:`ImageAugmentation`. The training images should always be square and of the
             same size. This is the size, in pixels, of the `width` and the `height` of the
             training images.
         """
        logger.debug("Initializing constants. training_size: %s", training_size)
        self._training_size = training_size
        coverage = int(self._training_size * self._coverage_ratio)

        # Color Aug
        clahe_base_contrast = training_size // 128
        # Target Images
        tgt_slices = slice(self._training_size // 2 - coverage // 2,
                           self._training_size // 2 + coverage // 2)

        # Random Warp
        warp_range_ = np.linspace(self._training_size // 2 - coverage // 2,
                                  self._training_size // 2 + coverage // 2, 5, dtype='float32')
        warp_mapx = np.broadcast_to(warp_range_, (self._batchsize, 5, 5)).astype("float32")
        warp_mapy = np.broadcast_to(warp_mapx[0].T, (self._batchsize, 5, 5)).astype("float32")

        warp_pad = int(1.25 * self._input_size)
        warp_slices = slice(warp_pad // 10, -warp_pad // 10)

        # Random Warp Landmarks
        p_mx = self._training_size - 1
        p_hf = (self._training_size // 2) - 1
        edge_anchors = np.array([(0, 0), (0, p_mx), (p_mx, p_mx), (p_mx, 0),
                                 (p_hf, 0), (p_hf, p_mx), (p_mx, p_hf), (0, p_hf)]).astype("int32")
        edge_anchors = np.broadcast_to(edge_anchors, (self._batchsize, 8, 2))
        grids = np.mgrid[0:p_mx:complex(self._training_size), 0:p_mx:complex(self._training_size)]

        self._constants = dict(clahe_base_contrast=clahe_base_contrast,
                               tgt_slices=tgt_slices,
                               warp_mapx=warp_mapx,
                               warp_mapy=warp_mapy,
                               warp_pad=warp_pad,
                               warp_slices=warp_slices,
                               warp_lm_edge_anchors=edge_anchors,
                               warp_lm_grids=grids)
        self.initialized = True
        logger.debug("Initialized constants: %s", self._constants)

    # <<< TARGET IMAGES >>> #
    def get_targets(self, batch):
        """ Returns the target images, and masks, if required.

        Parameters
        ----------
        batch: numpy.ndarray
            This should be a 4-dimensional array of training images in the format (`batchsize`,
            `height`, `width`, `channels`). Targets should be requested after performing image
            transformations but prior to performing warps.

        Returns
        -------
        dict
            The following keys will be within the returned dictionary:

            * **targets** (`list`) - A list of 4-dimensional ``numpy.ndarray`` s in the order \
            and size of each output of the model as defined in :attr:`output_shapes`. The \
            format of these arrays will be (`batchsize`, `height`, `width`, `3`). **NB:** \
            masks are not included in the ``targets`` list. If masks are to be included in the \
            output they will be returned as their own item from the ``masks`` key.

            * **masks** (`numpy.ndarray`) - A 4-dimensional array containing the target masks in \
            the format (`batchsize`, `height`, `width`, `1`). **NB:** This item will only exist \
            in the ``dict`` if a batch of 4 channel images has been passed in :attr:`batch`
        """
        logger.trace("Compiling targets")
        slices = self._constants["tgt_slices"]
        target_batch = [np.array([cv2.resize(image[slices, slices, :],
                                             (size, size),
                                             cv2.INTER_AREA)
                                  for image in batch])
                        for size in self._output_sizes]
        logger.trace("Target image shapes: %s",
                     [tgt.shape for tgt_images in target_batch for tgt in tgt_images])

        retval = self._separate_target_mask(target_batch)
        logger.trace("Final targets: %s",
                     {k: v.shape if isinstance(v, np.ndarray) else [img.shape for img in v]
                      for k, v in retval.items()})
        return retval </s> remove         for i, map_ in enumerate([mapx, mapy]):
            map_ = map_ + np.random.normal(size=(5, 5), scale=self.scale)
            interp[i] = cv2.resize(map_, (pad, pad))[slices, slices]  # pylint:disable=no-member
 </s> add         The color adjustment parameters are set in :file:`config.train.ini` </s> remove     def random_warp(self, image):
        """ get pair of random warped images from aligned face image """
        logger.trace("Randomly warping image")
        height, width = image.shape[0:2]
        coverage = self.get_coverage(image) // 2
        try:
            assert height == width and height % 2 == 0
        except AssertionError as err:
            msg = ("Training images should be square with an even number of pixels across each "
                   "side. An image was found with width: {}, height: {}."
                   "\nMost likely this is a frame rather than a face within your training set. "
                   "\nMake sure that the only images within your training set are faces generated "
                   "from the Extract process.".format(width, height))
            raise FaceswapError(msg) from err

        range_ = np.linspace(height // 2 - coverage, height // 2 + coverage, 5, dtype='float32')
        mapx = np.broadcast_to(range_, (5, 5)).copy()
        mapy = mapx.T
        # mapx, mapy = np.float32(np.meshgrid(range_,range_)) # instead of broadcast

        pad = int(1.25 * self.input_size)
        slices = slice(pad // 10, -pad // 10)
        dst_slices = [slice(0, (size + 1), (size // 4)) for size in self.output_sizes]
        interp = np.empty((2, self.input_size, self.input_size), dtype='float32')
 </s> add     # <<< COLOR AUGMENTATION >>> #
    def color_adjust(self, batch):
        """ Perform color augmentation on the passed in batch. </s> remove     def random_transform(self, image):
        """ Randomly transform an image """
        logger.trace("Randomly transforming image")
        height, width = image.shape[0:2]

        rotation_range = self.config.get("rotation_range", 10)
        rotation = np.random.uniform(-rotation_range, rotation_range)

        zoom_range = self.config.get("zoom_range", 5) / 100
        scale = np.random.uniform(1 - zoom_range, 1 + zoom_range)

        shift_range = self.config.get("shift_range", 5) / 100
        tnx = np.random.uniform(-shift_range, shift_range) * width
        tny = np.random.uniform(-shift_range, shift_range) * height

        mat = cv2.getRotationMatrix2D(  # pylint:disable=no-member
            (width // 2, height // 2), rotation, scale)
        mat[:, 2] += (tnx, tny)
        result = cv2.warpAffine(  # pylint:disable=no-member
            image, mat, (width, height),
            borderMode=cv2.BORDER_REPLICATE)  # pylint:disable=no-member

        logger.trace("Randomly transformed image")
        return result

    def do_random_flip(self, image):
        """ Perform flip on image if random number is within threshold """
        logger.trace("Randomly flipping image")
        random_flip = self.config.get("random_flip", 50) / 100
        if np.random.random() < random_flip:
            logger.trace("Flip within threshold. Flipping")
            retval = image[:, ::-1]
 </s> add         Returns the targets as a list of 4-dimensional ``numpy.ndarray`` s of shape (`batchsize`,
        `height`, `width`, 3). If the :attr:`batch` is 4 channels, then the masks will be split
        from the batch, with the largest output masks being returned in their own item.
        """
        batch = [tgt.astype("float32") / 255.0 for tgt in batch]
        if all(tgt.shape[-1] == 4 for tgt in batch):
            logger.trace("Batch contains mask")
            sizes = [item.shape[1] for item in batch]
            mask_batch = np.expand_dims(batch[sizes.index(max(sizes))][..., -1], axis=-1)
            batch = [item[..., :3] for item in batch]
            logger.trace("batch shapes: %s, mask_batch shape: %s",
                         [tgt.shape for tgt in batch], mask_batch.shape)
            retval = dict(targets=batch, masks=mask_batch) </s> remove         target_images = [cv2.warpAffine(image,  # pylint:disable=no-member
                                        mat,
                                        (self.output_sizes[idx], self.output_sizes[idx]))
                         for idx, mat in enumerate(mats)]
 </s> add         for image, rand in zip(batch, randoms):
            for idx in range(rand.shape[-1]):
                adjustment = rand[:, :, idx]
                if adjustment >= 0:
                    image[:, :, idx] = ((255 - image[:, :, idx]) * adjustment) + image[:, :, idx]
                else:
                    image[:, :, idx] = image[:, :, idx] * (1 + adjustment)
        return batch

    # <<< IMAGE AUGMENTATION >>> #
    def transform(self, batch):
        """ Perform random transformation on the passed in batch.

        The transformation parameters are set in :file:`config.train.ini`

        Parameters
        ----------
        batch: numpy.ndarray
            The batch should be a 4-dimensional array of shape (`batchsize`, `height`, `width`,
            `channels`) and in `BGR` format.

        Returns
        ----------
        numpy.ndarray
            A 4-dimensional array of the same shape as :attr:`batch` with transformation applied.
        """
        if self.is_display:
            return batch
        logger.trace("Randomly transforming image")
        rotation_range = self._config.get("rotation_range", 10)
        zoom_range = self._config.get("zoom_range", 5) / 100
        shift_range = self._config.get("shift_range", 5) / 100

        rotation = np.random.uniform(-rotation_range,
                                     rotation_range,
                                     size=self._batchsize).astype("float32")
        scale = np.random.uniform(1 - zoom_range,
                                  1 + zoom_range,
                                  size=self._batchsize).astype("float32")
        tform = np.random.uniform(
            -shift_range,
            shift_range,
            size=(self._batchsize, 2)).astype("float32") * self._training_size

        mats = np.array(
            [cv2.getRotationMatrix2D((self._training_size // 2, self._training_size // 2),
                                     rot,
                                     scl)
             for rot, scl in zip(rotation, scale)]).astype("float32")
        mats[..., 2] += tform

        batch = np.array([cv2.warpAffine(image,
                                         mat,
                                         (self._training_size, self._training_size),
                                         borderMode=cv2.BORDER_REPLICATE)
                          for image, mat in zip(batch, mats)])
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
  
          logger.trace("Target image shapes: %s", [tgt.shape for tgt in target_images])
          return self.compile_images(warped_image, target_images)
  
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
    def random_flip(self, batch):
        """ Perform random horizontal flipping on the passed in batch.
  
  
  def stack_images(images):
      """ Stack images """
      logger.debug("Stack images")
keep keep keep keep replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace keep keep keep keep keep
 Optimize Data Augmentation (#881)

* Move image utils to lib.image
* Add .pylintrc file
* Remove some cv2 pylint ignores
* TrainingData: Load images from disk in batches
* TrainingData: get_landmarks to batch
* TrainingData: transform and flip to batches
* TrainingData: Optimize color augmentation
* TrainingData: Optimize target and random_warp
* TrainingData - Convert _get_closest_match for batching
* TrainingData: Warp To Landmarks optimized
* Save models to threadpoolexecutor
* Move stack_images, Rename ImageManipulation. ImageAugmentation Docstrings
* Masks: Set dtype and threshold for lib.masks based on input face
* Docstrings and Documentation 
lib/training_data.py