1
0
Fork 0
mirror of https://github.com/deepfakes/faceswap synced 2025-06-07 10:43:27 -04:00
faceswap/tools/manual/globals.py

309 lines
12 KiB
Python

#!/usr/bin/env python3
""" Holds global tkinter variables and information pertaining to the entire Manual tool """
from __future__ import annotations
import logging
import os
import sys
import tkinter as tk
from dataclasses import dataclass, field
import cv2
import numpy as np
from lib.gui.utils import get_config
from lib.logger import parse_class_init
from lib.utils import VIDEO_EXTENSIONS
logger = logging.getLogger(__name__)
@dataclass
class CurrentFrame:
""" Dataclass for holding information about the currently displayed frame """
image: np.ndarray = field(default_factory=lambda: np.zeros(1))
""":class:`numpy.ndarry`: The currently displayed frame in original dimensions """
scale: float = 1.0
"""float: The scaling factor to use to resize the image to the display window """
interpolation: int = cv2.INTER_AREA
"""int: The opencv interpolator ID to use for resizing the image to the display window """
display_dims: tuple[int, int] = (0, 0)
"""tuple[int, int]`: The size of the currently displayed frame, in the display window """
filename: str = ""
"""str: The filename of the currently displayed frame """
def __repr__(self) -> str:
""" Clean string representation showing numpy arrays as shape and dtype
Returns
-------
str
Loggable representation of the dataclass
"""
properties = [f"{k}={(v.shape, v.dtype) if isinstance(v, np.ndarray) else v}"
for k, v in self.__dict__.items()]
return f"{self.__class__.__name__} ({', '.join(properties)}"
@dataclass
class TKVars:
""" Holds the global TK Variables """
frame_index: tk.IntVar
""":class:`tkinter.IntVar`: The absolute frame index of the currently displayed frame"""
transport_index: tk.IntVar
""":class:`tkinter.IntVar`: The transport index of the currently displayed frame when filters
have been applied """
face_index: tk.IntVar
""":class:`tkinter.IntVar`: The face index of the currently selected face"""
filter_distance: tk.IntVar
""":class:`tkinter.IntVar`: The amount to filter by distance"""
update: tk.BooleanVar
""":class:`tkinter.BooleanVar`: Whether an update has been performed """
update_active_viewport: tk.BooleanVar
""":class:`tkinter.BooleanVar`: Whether the viewport needs updating """
is_zoomed: tk.BooleanVar
""":class:`tkinter.BooleanVar`: Whether the main window is zoomed in to a face or out to a
full frame"""
filter_mode: tk.StringVar
""":class:`tkinter.StringVar`: The currently selected filter mode """
faces_size: tk.StringVar
""":class:`tkinter.StringVar`: The pixel size of faces in the viewport """
def __repr__(self) -> str:
""" Clean string representation showing variable type as well as their value
Returns
-------
str
Loggable representation of the dataclass
"""
properties = [f"{k}={v.__class__.__name__}({v.get()})" for k, v in self.__dict__.items()]
return f"{self.__class__.__name__} ({', '.join(properties)}"
class TkGlobals():
""" Holds Tkinter Variables and other frame information that need to be accessible from all
areas of the GUI.
Parameters
----------
input_location: str
The location of the input folder of frames or video file
"""
def __init__(self, input_location: str) -> None:
logger.debug(parse_class_init(locals()))
self._tk_vars = self._get_tk_vars()
self._is_video = self._check_input(input_location)
self._frame_count = 0 # set by FrameLoader
self._frame_display_dims = (int(round(896 * get_config().scaling_factor)),
int(round(504 * get_config().scaling_factor)))
self._current_frame = CurrentFrame()
logger.debug("Initialized %s", self.__class__.__name__)
@classmethod
def _get_tk_vars(cls) -> TKVars:
""" Create and initialize the tkinter variables.
Returns
-------
:class:`TKVars`
The global tkinter variables
"""
retval = TKVars(frame_index=tk.IntVar(value=0),
transport_index=tk.IntVar(value=0),
face_index=tk.IntVar(value=0),
filter_distance=tk.IntVar(value=10),
update=tk.BooleanVar(value=False),
update_active_viewport=tk.BooleanVar(value=False),
is_zoomed=tk.BooleanVar(value=False),
filter_mode=tk.StringVar(),
faces_size=tk.StringVar())
logger.debug(retval)
return retval
@property
def current_frame(self) -> CurrentFrame:
""" :class:`CurrentFrame`: The currently displayed frame in the frame viewer with it's
meta information. """
return self._current_frame
@property
def frame_count(self) -> int:
""" int: The total number of frames for the input location """
return self._frame_count
@property
def frame_display_dims(self) -> tuple[int, int]:
""" tuple: The (`width`, `height`) of the video display frame in pixels. """
return self._frame_display_dims
@property
def is_video(self) -> bool:
""" bool: ``True`` if the input is a video file, ``False`` if it is a folder of images. """
return self._is_video
# TK Variables that need to be exposed
@property
def var_full_update(self) -> tk.BooleanVar:
""" :class:`tkinter.BooleanVar`: Flag to indicate that whole GUI should be refreshed """
return self._tk_vars.update
@property
def var_transport_index(self) -> tk.IntVar:
""" :class:`tkinter.IntVar`: The current index of the display frame's transport slider. """
return self._tk_vars.transport_index
@property
def var_frame_index(self) -> tk.IntVar:
""" :class:`tkinter.IntVar`: The current absolute frame index of the currently
displayed frame. """
return self._tk_vars.frame_index
@property
def var_filter_distance(self) -> tk.IntVar:
""" :class:`tkinter.IntVar`: The variable holding the currently selected threshold
distance for misaligned filter mode. """
return self._tk_vars.filter_distance
@property
def var_filter_mode(self) -> tk.StringVar:
""" :class:`tkinter.StringVar`: The variable holding the currently selected navigation
filter mode. """
return self._tk_vars.filter_mode
@property
def var_faces_size(self) -> tk.StringVar:
""" :class:`tkinter..IntVar`: The variable holding the currently selected Faces Viewer
thumbnail size. """
return self._tk_vars.faces_size
@property
def var_update_active_viewport(self) -> tk.BooleanVar:
""" :class:`tkinter.BooleanVar`: Boolean Variable that is traced by the viewport's active
frame to update. """
return self._tk_vars.update_active_viewport
# Raw values returned from TK Variables
@property
def face_index(self) -> int:
""" int: The currently displayed face index when in zoomed mode. """
return self._tk_vars.face_index.get()
@property
def frame_index(self) -> int:
""" int: The currently displayed frame index. NB This returns -1 if there are no frames
that meet the currently selected filter criteria. """
return self._tk_vars.frame_index.get()
@property
def is_zoomed(self) -> bool:
""" bool: ``True`` if the frame viewer is zoomed into a face, ``False`` if the frame viewer
is displaying a full frame. """
return self._tk_vars.is_zoomed.get()
@staticmethod
def _check_input(frames_location: str) -> bool:
""" Check whether the input is a video
Parameters
----------
frames_location: str
The input location for video or images
Returns
-------
bool: 'True' if input is a video 'False' if it is a folder.
"""
if os.path.isdir(frames_location):
retval = False
elif os.path.splitext(frames_location)[1].lower() in VIDEO_EXTENSIONS:
retval = True
else:
logger.error("The input location '%s' is not valid", frames_location)
sys.exit(1)
logger.debug("Input '%s' is_video: %s", frames_location, retval)
return retval
def set_face_index(self, index: int) -> None:
""" Set the currently selected face index
Parameters
----------
index: int
The currently selected face index
"""
logger.trace("Setting face index from %s to %s", # type:ignore[attr-defined]
self.face_index, index)
self._tk_vars.face_index.set(index)
def set_frame_count(self, count: int) -> None:
""" Set the count of total number of frames to :attr:`frame_count` when the
:class:`FramesLoader` has completed loading.
Parameters
----------
count: int
The number of frames that exist for this session
"""
logger.debug("Setting frame_count to : %s", count)
self._frame_count = count
def set_current_frame(self, image: np.ndarray, filename: str) -> None:
""" Set the frame and meta information for the currently displayed frame. Populates the
attribute :attr:`current_frame`
Parameters
----------
image: :class:`numpy.ndarray`
The image used to display in the Frame Viewer
filename: str
The filename of the current frame
"""
scale = min(self.frame_display_dims[0] / image.shape[1],
self.frame_display_dims[1] / image.shape[0])
self._current_frame.image = image
self._current_frame.filename = filename
self._current_frame.scale = scale
self._current_frame.interpolation = cv2.INTER_CUBIC if scale > 1.0 else cv2.INTER_AREA
self._current_frame.display_dims = (int(round(image.shape[1] * scale)),
int(round(image.shape[0] * scale)))
logger.trace(self._current_frame) # type:ignore[attr-defined]
def set_frame_display_dims(self, width: int, height: int) -> None:
""" Set the size, in pixels, of the video frame display window and resize the displayed
frame.
Used on a frame resize callback, sets the :attr:frame_display_dims`.
Parameters
----------
width: int
The width of the frame holding the video canvas in pixels
height: int
The height of the frame holding the video canvas in pixels
"""
self._frame_display_dims = (int(width), int(height))
image = self._current_frame.image
scale = min(self.frame_display_dims[0] / image.shape[1],
self.frame_display_dims[1] / image.shape[0])
self._current_frame.scale = scale
self._current_frame.interpolation = cv2.INTER_CUBIC if scale > 1.0 else cv2.INTER_AREA
self._current_frame.display_dims = (int(round(image.shape[1] * scale)),
int(round(image.shape[0] * scale)))
logger.trace(self._current_frame) # type:ignore[attr-defined]
def set_zoomed(self, state: bool) -> None:
""" Set the current zoom state
Parameters
----------
state: bool
``True`` for zoomed ``False`` for full frame
"""
logger.trace("Setting zoom state from %s to %s", # type:ignore[attr-defined]
self.is_zoomed, state)
self._tk_vars.is_zoomed.set(state)