          io_args: tuple, optional
              Any arguments that need to be provided to the background function
          """
          logger.debug("Threading task: (Task: '%s')", task)
          io_args = tuple() if io_args is None else (io_args, )
          func = getattr(self, f"_{task}")
          io_thread = MultiThread(func, *io_args, thread_count=1)
          io_thread.start()
          self._threads.append(io_thread)
  
 
</s> remove     def _reset_cache(self, set_flag):
        """ In the event that a legacy extracted face has been seen, and centering is not legacy
        the cache will need to be reset for legacy centering.
 </s> add         If masks are not being used then this function returns having done nothing </s> remove class _Cache():
    """ A thread safe mechanism for collecting and holding face meta information (masks, "
    "alignments data etc.) for multiple :class:`TrainingDataGenerator`s.
 </s> add logger = logging.getLogger(__name__)
ConfigType = Dict[str, Union[bool, int, float, str]]  # TODO Dataclass
BatchType = Tuple[np.ndarray, List[np.ndarray]] </s> remove class _LandmarksMask():  # pylint:disable=too-few-public-methods
    """ Create a single channel mask from aligned landmark points.

    size: tuple
        The (height, width) shape tuple that the mask should be returned as
    points: list
        A list of landmark points that correspond to the given shape tuple to create
        the mask. Each item in the list should be a :class:`numpy.ndarray` that a filled
        convex polygon will be created from
    dilation: int, optional
        The amount of dilation to apply to the mask. `0` for none. Default: `0`
    blur_kernel: int, optional
        The kernel size for applying gaussian blur to apply to the mask. `0` for none. Default: `0`
    """
    def __init__(self, size, points, dilation=0, blur_kernel=0):
        logger.trace("Initializing: %s: (size: %s, points: %s, dilation: %s, blur_kernel: %s)",
                     self.__class__.__name__, size, points, dilation, blur_kernel)
        self._size = size
        self._points = points
        self._dilation = dilation
        self._blur_kernel = blur_kernel
        self._mask = None
        logger.trace("Initialized: %s", self.__class__.__name__)

    def get(self, as_zip=False):
        """ Obtain the mask.

        Parameters
        ----------
        as_zip: bool, optional
            ``True`` if the mask should be returned zipped otherwise ``False``

        Returns
        -------
        :class:`numpy.ndarray` or zipped array
            The mask as a single channel image of the given :attr:`size` dimension. If
            :attr:`as_zip` is ``True`` then the :class:`numpy.ndarray` will be contained within a
            zipped container
        """
        if not np.any(self._mask):
            self._generate_mask()
        retval = compress(self._mask) if as_zip else self._mask
        logger.trace("as_zip: %s, retval type: %s", as_zip, type(retval))
        return retval

    def _generate_mask(self):
        """ Generate the mask.

        Creates the mask applying any requested dilation and blurring and assigns to
        :attr:`_mask`

        Returns
        -------
        :class:`numpy.ndarray`
            The mask as a single channel image of the given :attr:`size` dimension.
        """
        mask = np.zeros((self._size) + (1, ), dtype="float32")
        for landmarks in self._points:
            lms = np.rint(landmarks).astype("int")
            cv2.fillConvexPoly(mask, cv2.convexHull(lms), 1.0, lineType=cv2.LINE_AA)
        if self._dilation != 0:
            mask = cv2.dilate(mask,
                              cv2.getStructuringElement(cv2.MORPH_ELLIPSE,
                                                        (self._dilation, self._dilation)),
                              iterations=1)
        if self._blur_kernel != 0:
            mask = BlurMask("gaussian", mask, self._blur_kernel).blurred
        logger.trace("mask: (shape: %s, dtype: %s)", mask.shape, mask.dtype)
        self._mask = (mask * 255.0).astype("uint8")


 </s> add  </s> remove         if output is None:
 </s> add         if not output:
        io_args = tuple() if io_args is None else io_args
          io_args: tuple, optional
              Any arguments that need to be provided to the background function
          """
          logger.debug("Threading task: (Task: '%s')", task)
        io_args = tuple() if io_args is None else io_args
          func = getattr(self, f"_{task}")
          io_thread = MultiThread(func, *io_args, thread_count=1)
          io_thread.start()
          self._threads.append(io_thread)
  
keep keep keep keep replace keep keep keep keep keep
 Data Augmentation update (#1263)

- lib.detected_face
    - Subclass Masks for Landmark based masks
    - Add training mask propery + methods to DetectedFace
  - lib.training_training
    - subclass TrainingDataGenerator for training and preview data
    - Split cache into own module
    - Reduce thread count to 1 to prevent image corruption + data re-use
    - Process on largest model input/output size rather than stored image size
    - Size and crop masks during caching stage
    - Implement ring buffer for data flow
    - Fix preview reload bug
  - augmentation
    - typing
    - switch color aug order
    - better initialization
    - Fix warp + landmark warp to correctly apply at different image scales
    - Slightly improved warp caching
    - Don't store whether image is_preview. Handle all data as training images implicitly
  - plugins.trainer: Typing and fixes to work with trainingdata refactor 
scripts/extract.py