              The name to display in the window header and for window identification
          shape: tuple
              The (`rows`, `columns`, `channels`) of the image to be displayed
          """
          logger.debug("Creating figure '%s' for image shape %s", name, image_shape)
          if not self._dpi:
              self._dpi = get_dpi()
          height, width = image_shape[:2]
          size = width / self._dpi, height / self._dpi
          fig = plt.figure(name, figsize=size)
          axes = plt.Axes(fig, [0., 0., 1., 1.])  # Remove axes and whitespace
          axes.set_axis_off()
          fig.add_axes(axes)
          fig.canvas.mpl_connect("key_press_event", self._on_key_press)
          fig.canvas.mpl_connect("close_event", self._on_close)
          logger.debug("Created display figure of size: %s", size)
          self._images[name] = (fig, size)
  
      def _set_resize_callback(self):
          """ Sets the resize callback if displaying preview at actual size or removes it if
          displaying at screen-fit size. """
          if self._triggers["full_size"]:
              logger.debug("Setting resize callback for actual size display")
              for fig, size in self._images.values():
                  self._resize_ids.append((fig, fig.canvas.mpl_connect("resize_event",
                                                                       self._on_resize)))
                  fig.set_size_inches(size)
          else:
              logger.debug("Removing resize callback for screen-fit display")
              for fig, cid in self._resize_ids:
                  fig.canvas.mpl_disconnect(cid)
              self._resize_ids = []
  
      def _on_key_press(self, event: backend_bases.KeyEvent) -> None:
          """ Callbacks for keypresses to update the requested trigger.
  
          - `F` (toggle full-size/fit to window)
          - `M` (toggle mask),
          - `R` (refresh preview),
          - `S` (save now)
          - `Enter` (save and exit)
  
          Parameters
          ----------
          event:
              The key press received
 
</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         Parameters
        ----------
        event:
            The key press received
        """
        key = event.key.lower()
        if key not in self._callbacks:
 </s> add     def shutdown(self) -> None:
        """ Send a signal to shutdown the preview window. """
        if not self._active: </s> remove 
        logger.debug("Preview window keypress '%s' received", key)
        if key == "r":
            print("\n")  # Let log print on different line from loss output
            logger.info("Refresh preview requested...")

        with self._lock:
            if key == "f":
                self._toggle_size()
            else:
                self._triggers[self._callbacks[key]] = True

    def _on_resize(self,
                   event: backend_bases.ResizeEvent) -> None:  # noqa # pylint:disable=unused-argument
        """ If the display is set to `actual size` then the image needs to be resized on any window
        resize event. """
        for fig, size in self._images.values():
            fig.set_size_inches(size)

    def _on_close(self,
                   event: backend_bases.CloseEvent) -> None:  # noqa # pylint:disable=unused-argument
        """ Force an update when the figure has been closed to relaunch it. """
        logger.debug("Preview close detected")
        with self._lock:
            self._needs_update = True
 </s> add         logger.debug("Sending shutdown to preview viewer")
        self._triggers["shutdown"].set() </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 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
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
              The name to display in the window header and for window identification
          shape: tuple
              The (`rows`, `columns`, `channels`) of the image to be displayed
          """
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
        if not self._active:
            return None
        thread = FSThread(target=Preview,
                          name="preview",
                          args=(self._buffer, ),
                          kwargs=dict(triggers=self._triggers))
        thread.start()
        return thread
  
          Parameters
          ----------
          event:
              The key press received
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 keep keep keep keep keep
 Training - Use custom preview pop-out 
scripts/train.py