                  sleep(1)
              except KeyboardInterrupt:
                  logger.debug("Keyboard Interrupt received")
                  break
          keypress.set_normal_term()
          logger.debug("Closed Monitor")
          return err
  
      def _show(self, image: np.ndarray, name: str = "") -> None:
          """ Generate the preview and write preview file output.
 
</s> remove                 # GUI Preview trigger update monitor
                self._process_gui_triggers()

 </s> add  </s> remove     def request_refresh(self) -> None:
        """ Handle a GUI trigger or a training thread trigger (after a mask toggle) request to set
        the refresh trigger to ``True`` to request a refresh on the next pass of the
        training loop. """
        with self._lock:
            self._triggers["refresh"] = True

    def request_mask_toggle(self) -> None:
        """ Handle a GUI trigger request to set the mask toggle to ``True`` to
        request a mask toggle on next pass of the training loop. """
        logger.verbose("Toggle mask display requested...")  # type:ignore
        with self._lock:
            self._triggers["toggle_mask"] = True

    def add_image(self, name: str, image: np.ndarray) -> None:
        """ Add a preview image to the preview buffer.

        Parameters
        ----------
        name: str
            The name of the preview image to add to the buffer
        image: :class:`numpy.ndarray`
            The preview image to add to the buffer in BGR format.
        """
        with self._lock:
            logger.debug("Adding image '%s' of shape %s to preview buffer", name, image.shape)
            self._preview_buffer[name] = image[..., 2::-1]  # Switch to RGB
            self._needs_update = True

    def display_preview(self) -> None:
        """ Display an image preview in a resizable window. """
        if self._needs_update:
            logger.debug("Updating preview")
            with self._lock:
                for name, image in self._preview_buffer.items():
                    if (name not in self._images or  # new preview or preview was closed
                            not plt.fignum_exists(self._images[name][0].number)):
                        self._create_resizable_window(name, image.shape)
                        if self._triggers["full_size"]:  # Can only be true if preview was closed
                            self._set_resize_callback()
                    plt.figure(name)
                    plt.imshow(image)
                self._needs_update = False
            plt.show(block=False)
            logger.debug("preview updated")  # type: ignore
        plt.pause(0.1)

    def _create_resizable_window(self, name: str, image_shape: tuple) -> None:
        """ Create a resizable Matplotlib window to hold the preview image.
 </s> add     def _launch_thread(self) -> Optional[FSThread]:
        """ Launch the preview viewer in it's own thread if preview has been selected </s> remove         Parameters
        ----------
        name: str
            The name to display in the window header and for window identification
        shape: tuple
            The (`rows`, `columns`, `channels`) of the image to be displayed
 </s> add         Returns
        -------
        :class:`lib.multithreading.FSThread` or ``None``
            The thread that holds the preview viewer if preview is selected otherwise ``None`` </s> remove     def get_trainer(name: str, disable_logging: bool = False) -> "TrainerBase":
 </s> add     def get_trainer(name: str, disable_logging: bool = False) -> Type["TrainerBase"]: </s> remove class Preview():
    """ Holds the pop up preview window and options relating to the preview in the window and the
    GUI. Thread safe to take requests from the main thread and the training thread. """
    def __init__(self) -> None:
        self._lock = Lock()
        self._dpi: float = 0.0
        self._triggers: Dict[str, bool] = dict(toggle_mask=False,
                                               full_size=False,
                                               refresh=False,
                                               save=False,
                                               quit=False)
        self._needs_update: bool = False
        self._preview_buffer: Dict[str, np.ndarray] = {}
        self._images: Dict[str, Tuple[figure.Figure, Tuple[float, float]]] = {}
        self._resize_ids: List[Tuple[figure.Figure, int]] = []
        self._callbacks = dict(f="full_size",
                               m="toggle_mask",
                               r="refresh",
                               s="save",
                               enter="quit")
        self._configure_matplotlib()

    def _toggle_size(self) -> None:  # pylint:disable=unused-argument
        """ Toggle between actual size and screen-fit size. """
        self._triggers["full_size"] = not self._triggers["full_size"]
        self._set_resize_callback()

    @classmethod
    def _configure_matplotlib(cls):
        """ Remove `F`, 'S' and 'R' from their default bindings and stop Matplotlib from stealing
        focus """
        rcParams["keymap.fullscreen"] = [k for k in rcParams["keymap.fullscreen"] if k != "f"]
        rcParams["keymap.save"] = [k for k in rcParams["keymap.save"] if k != "s"]
        rcParams["keymap.home"] = [k for k in rcParams["keymap.home"] if k != "r"]
        rcParams["figure.raise_window"] = False
 </s> add class PreviewInterface():
    """ Run the preview window in a thread and interface with it
        self._preview.shutdown()
                  sleep(1)
              except KeyboardInterrupt:
                  logger.debug("Keyboard Interrupt received")
                  break
        self._preview.shutdown()
          keypress.set_normal_term()
          logger.debug("Closed Monitor")
          return err
  
      def _show(self, image: np.ndarray, name: str = "") -> None:
          """ Generate the preview and write preview file output.
keep keep keep add keep keep keep keep keep keep
 Training - Use custom preview pop-out 
scripts/train.py