1
0
Fork 0
mirror of https://github.com/deepfakes/faceswap synced 2025-06-07 10:43:27 -04:00
faceswap/lib/training/preview_cv.py
torzdf 6a3b674bef
Rebase code (#1326)
* Remove tensorflow_probability requirement

* setup.py - fix progress bars

* requirements.txt: Remove pre python 3.9 packages

* update apple requirements.txt

* update INSTALL.md

* Remove python<3.9 code

* setup.py - fix Windows Installer

* typing: python3.9 compliant

* Update pytest and readthedocs python versions

* typing fixes

* Python Version updates
  - Reduce max version to 3.10
  - Default to 3.10 in installers
  - Remove incompatible 3.11 tests

* Update dependencies

* Downgrade imageio dep for Windows

* typing: merge optional unions and fixes

* Updates
  - min python version 3.10
  - typing to python 3.10 spec
  - remove pre-tf2.10 code
  - Add conda tests

* train: re-enable optimizer saving

* Update dockerfiles

* Update setup.py
  - Apple Conda deps to setup.py
  - Better Cuda + dependency handling

* bugfix: Patch logging to prevent Autograph errors

* Update dockerfiles

* Setup.py - Setup.py - stdout to utf-8

* Add more OSes to github Actions

* suppress mac-os end to end test
2023-06-27 11:27:47 +01:00

189 lines
7 KiB
Python

#!/usr/bin/python
""" The pop up preview window for Faceswap.
If Tkinter is installed, then this will be used to manage the preview image, otherwise we
fallback to opencv's imshow
"""
from __future__ import annotations
import logging
import typing as T
from threading import Event, Lock
from time import sleep
import cv2
if T.TYPE_CHECKING:
from collections.abc import Generator
import numpy as np
logger = logging.getLogger(__name__)
TriggerType = dict[T.Literal["toggle_mask", "refresh", "save", "quit", "shutdown"], Event]
TriggerKeysType = T.Literal["m", "r", "s", "enter"]
TriggerNamesType = T.Literal["toggle_mask", "refresh", "save", "quit"]
class PreviewBuffer():
""" A thread safe class for holding preview images """
def __init__(self) -> None:
logger.debug("Initializing: %s", self.__class__.__name__)
self._images: dict[str, np.ndarray] = {}
self._lock = Lock()
self._updated = Event()
logger.debug("Initialized: %s", self.__class__.__name__)
@property
def is_updated(self) -> bool:
""" bool: ``True`` when new images have been loaded into the preview buffer """
return self._updated.is_set()
def add_image(self, name: str, image: np.ndarray) -> None:
""" Add an image to the preview buffer in a thread safe way """
logger.debug("Adding image: (name: '%s', shape: %s)", name, image.shape)
with self._lock:
self._images[name] = image
logger.debug("Added images: %s", list(self._images))
self._updated.set()
def get_images(self) -> Generator[tuple[str, np.ndarray], None, None]:
""" Get the latest images from the preview buffer. When iterator is exhausted clears the
:attr:`updated` event.
Yields
------
name: str
The name of the image
:class:`numpy.ndarray`
The image in BGR format
"""
logger.debug("Retrieving images: %s", list(self._images))
with self._lock:
for name, image in self._images.items():
logger.debug("Yielding: '%s' (%s)", name, image.shape)
yield name, image
if self.is_updated:
logger.debug("Clearing updated event")
self._updated.clear()
logger.debug("Retrieved images")
class PreviewBase(): # pylint:disable=too-few-public-methods
""" Parent class for OpenCV and Tkinter Preview Windows
Parameters
----------
preview_buffer: :class:`PreviewBuffer`
The thread safe object holding the preview images
triggers: dict, optional
Dictionary of event triggers for pop-up preview. Not required when running inside the GUI.
Default: `None`
"""
def __init__(self,
preview_buffer: PreviewBuffer,
triggers: TriggerType | None = None) -> None:
logger.debug("Initializing %s parent (triggers: %s)",
self.__class__.__name__, triggers)
self._triggers = triggers
self._buffer = preview_buffer
self._keymaps: dict[TriggerKeysType, TriggerNamesType] = {"m": "toggle_mask",
"r": "refresh",
"s": "save",
"enter": "quit"}
self._title = ""
logger.debug("Initialized %s parent", self.__class__.__name__)
@property
def _should_shutdown(self) -> bool:
""" bool: ``True`` if the preview has received an external signal to shutdown otherwise
``False`` """
if self._triggers is None or not self._triggers["shutdown"].is_set():
return False
logger.debug("Shutdown signal received")
return True
def _launch(self) -> None:
""" Wait until an image is loaded into the preview buffer and call the child's
:func:`_display_preview` function """
logger.debug("Launching %s", self.__class__.__name__)
while True:
if not self._buffer.is_updated:
logger.debug("Waiting for preview image")
sleep(1)
continue
break
logger.debug("Launching preview")
self._display_preview()
def _display_preview(self) -> None:
""" Override for preview viewer's display loop """
raise NotImplementedError()
class PreviewCV(PreviewBase): # pylint:disable=too-few-public-methods
""" Simple fall back preview viewer using OpenCV for when TKinter is not available
Parameters
----------
preview_buffer: :class:`PreviewBuffer`
The thread safe object holding the preview images
triggers: dict
Dictionary of event triggers for pop-up preview.
"""
def __init__(self,
preview_buffer: PreviewBuffer,
triggers: TriggerType) -> None:
logger.debug("Unable to import Tkinter. Falling back to OpenCV")
super().__init__(preview_buffer, triggers=triggers)
self._triggers: TriggerType = self._triggers
self._windows: list[str] = []
self._lookup = {ord(key): val
for key, val in self._keymaps.items() if key != "enter"}
self._lookup[ord("\n")] = self._keymaps["enter"]
self._lookup[ord("\r")] = self._keymaps["enter"]
self._launch()
@property
def _window_closed(self) -> bool:
""" bool: ``True`` if any window has been closed otherwise ``False`` """
retval = any(cv2.getWindowProperty(win, cv2.WND_PROP_VISIBLE) < 1 for win in self._windows)
if retval:
logger.debug("Window closed detected")
return retval
def _check_keypress(self, key: int):
""" Check whether we have received a valid key press from OpenCV window and handle
accordingly.
Parameters
----------
key_press: int
The key press received from OpenCV
"""
if not key or key == -1 or key not in self._lookup:
return
if key == ord("r"):
print("") # Let log print on different line from loss output
logger.info("Refresh preview requested...")
self._triggers[self._lookup[key]].set()
logger.debug("Processed keypress '%s'. Set event for '%s'", key, self._lookup[key])
def _display_preview(self):
""" Handle the displaying of the images currently in :attr:`_preview_buffer`"""
while True:
if self._buffer.is_updated or self._window_closed:
for name, image in self._buffer.get_images():
logger.debug("showing image: '%s' (%s)", name, image.shape)
cv2.imshow(name, image)
self._windows.append(name)
key = cv2.waitKey(1000)
self._check_keypress(key)
if self._triggers["shutdown"].is_set():
logger.debug("Shutdown received")
break
logger.debug("%s shutdown", self.__class__.__name__)