/
githubmirror
/
faceswap
Обзор
Документация
Войти
/
githubmirror
/
faceswap
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/tools/preview/viewer_test.py
493 строки
20 KB
torzdf
Alignments: Migrate typed-dicts to dataclasses
05 апр 2026, 14:10
05 апр 2026, 14:10
32f427c
Код
Авторство
О чём код?
#!/usr/bin python3 """ Pytest unit tests for :mod:`tools.preview.viewer` """ from __future__ import annotations import tkinter as tk import typing as T from tkinter import ttk from unittest.mock import MagicMock import pytest import pytest_mock import numpy as np from PIL import ImageTk from lib.logger import log_setup # Need to setup logging to avoid trace/verbose errors log_setup("DEBUG", "pytest_viewer.log", "PyTest, False") from lib.utils import get_backend # pylint:disable=wrong-import-position # noqa from tools.preview.viewer import _Faces, FacesDisplay, ImagesCanvas # pylint:disable=wrong-import-position # noqa if T.TYPE_CHECKING: from lib.align.aligned_face import CenteringType # pylint:disable=protected-access def test__faces(): """ Test the :class:`~tools.preview.viewer._Faces dataclass initializes correctly """ faces = _Faces(5, 64) assert isinstance(faces.filenames, list) and not faces.filenames assert isinstance(faces.matrix, np.ndarray) assert isinstance(faces.src, np.ndarray) assert isinstance(faces.dst, np.ndarray) _PARAMS = ((3, 448), (4, 333), (5, 254), (6, 128)) # columns/face_size _IDS = [f"cols:{c},size:{s}[{get_backend().upper()}]" for c, s in _PARAMS] class TestFacesDisplay(): """ Test :class:`~tools.preview.viewer.FacesDisplay """ _padding = 64 def get_faces_display_instance(self, columns: int = 5, face_size: int = 256) -> FacesDisplay: """ Obtain an instance of :class:`~tools.preview.viewer.FacesDisplay` with the given column and face size layout. Parameters ---------- columns: int, optional The number of columns to display in the viewer, default: 5 face_size: int, optional The size of each face image to be displayed in the viewer, default: 256 Returns ------- :class:`~tools.preview.viewer.FacesDisplay` An instance of the FacesDisplay class at the given settings """ app = MagicMock() retval = FacesDisplay(app, face_size, self._padding, columns) retval._faces = _Faces(columns, face_size) return retval def test_init(self) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` __init__ method """ f_display = self.get_faces_display_instance(face_size=256) assert f_display._size == 256 assert f_display._padding == self._padding assert isinstance(f_display._app, MagicMock) assert f_display._display_dims == (1, 1) assert isinstance(f_display._faces, _Faces) assert f_display._centering is None assert f_display._faces_source.size == 0 assert f_display._faces_dest.size == 0 assert f_display._tk_image is None assert f_display.update_source is False assert not f_display.source and isinstance(f_display.source, list) assert not f_display.destination and isinstance(f_display.destination, list) @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test__total_columns(self, columns: int, face_size: int) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` _total_columns property is correctly calculated Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer """ f_display = self.get_faces_display_instance(columns, face_size) f_display.source = [None for _ in range(columns)] # type:ignore assert f_display._total_columns == columns def test_set_centering(self) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` set_centering_offset method """ f_display = self.get_faces_display_instance() assert f_display._centering is None centering: CenteringType = "legacy" f_display.set_centering_offset(centering, 0.80) assert f_display._centering == centering assert f_display._y_offset == 0.80 def test_set_display_dimensions(self) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` set_display_dimensions method """ f_display = self.get_faces_display_instance() assert f_display._display_dims == (1, 1) dimensions = (800, 600) f_display.set_display_dimensions(dimensions) assert f_display._display_dims == dimensions # TODO remove the next line that suppresses a weird pytest bug when it tears down the tempdir @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test_update_tk_image(self, columns: int, face_size: int, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` update_tk_image method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer mocker: :class:`pytest_mock.MockerFixture` Mocker for checking _build_faces_image method called """ f_display = self.get_faces_display_instance(columns, face_size) f_display._build_faces_image = T.cast(MagicMock, mocker.MagicMock()) # type:ignore f_display._get_scale_size = T.cast(MagicMock, # type:ignore mocker.MagicMock(return_value=(128, 128))) f_display._faces_source = np.zeros((face_size, face_size, 3), dtype=np.uint8) f_display._faces_dest = np.zeros((face_size, face_size, 3), dtype=np.uint8) try: tk.Tk() # tkinter instance needed for image creation except tk.TclError: # Some Windows runners arbitrarily don't install Tk correctly pytest.skip("Tk not available on this system") f_display.update_tk_image() f_display._build_faces_image.assert_called_once() f_display._get_scale_size.assert_called_once() assert isinstance(f_display._tk_image, ImageTk.PhotoImage) assert f_display._tk_image.width() == 128 assert f_display._tk_image.height() == 128 assert f_display.tk_image == f_display._tk_image # public property test @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test_get_scale_size(self, columns: int, face_size: int) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` get_scale_size method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer """ f_display = self.get_faces_display_instance(columns, face_size) f_display.set_display_dimensions((800, 600)) img = np.zeros((face_size, face_size, 3), dtype=np.uint8) size = f_display._get_scale_size(img) assert size == (600, 600) @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test__build_faces_image(self, columns: int, face_size: int, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` _build_faces_image method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer mocker: :class:`pytest_mock.MockerFixture` Mocker for checking internal methods called """ header_size = 32 f_display = self.get_faces_display_instance(columns, face_size) f_display._faces_from_frames = T.cast(MagicMock, mocker.MagicMock()) # type:ignore f_display._header_text = T.cast( # type:ignore MagicMock, mocker.MagicMock(return_value=np.random.rand(header_size, face_size * columns, 3))) f_display._draw_rect = T.cast(MagicMock, # type:ignore mocker.MagicMock(side_effect=lambda x: x)) # Test full update f_display.update_source = True f_display._build_faces_image() f_display._faces_from_frames.assert_called_once() f_display._header_text.assert_called_once() assert f_display._draw_rect.call_count == columns * 2 # src + dst assert f_display._faces_source.shape == (face_size + header_size, face_size * columns, 3) assert f_display._faces_dest.shape == (face_size, face_size * columns, 3) f_display._faces_from_frames.reset_mock() f_display._header_text.reset_mock() f_display._draw_rect.reset_mock() # Test dst update only f_display.update_source = False f_display._build_faces_image() f_display._faces_from_frames.assert_called_once() assert not f_display._header_text.called assert f_display._draw_rect.call_count == columns # dst only assert f_display._faces_dest.shape == (face_size, face_size * columns, 3) @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test_faces__from_frames(self, columns, face_size, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` _from_frames method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer mocker: :class:`pytest_mock.MockerFixture` Mocker for checking _build_faces_image method called """ f_display = self.get_faces_display_instance(columns, face_size) f_display.source = [mocker.MagicMock() for _ in range(3)] f_display.destination = [np.random.rand(face_size, face_size, 3) for _ in range(3)] f_display._crop_source_faces = T.cast(MagicMock, mocker.MagicMock()) # type:ignore f_display._crop_destination_faces = T.cast(MagicMock, mocker.MagicMock()) # type:ignore # Both src + dst f_display.update_source = True f_display._faces_from_frames() f_display._crop_source_faces.assert_called_once() f_display._crop_destination_faces.assert_called_once() f_display._crop_source_faces.reset_mock() f_display._crop_destination_faces.reset_mock() # Just dst f_display.update_source = False f_display._faces_from_frames() assert not f_display._crop_source_faces.called f_display._crop_destination_faces.assert_called_once() @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test__crop_source_faces(self, columns: int, face_size: int, monkeypatch: pytest.MonkeyPatch, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` _crop_source_faces method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer monkeypatch: :class:`pytest.MonkeyPatch` For patching the transform_image function mocker: :class:`pytest_mock.MockerFixture` Mocker for mocking various internal methods """ f_display = self.get_faces_display_instance(columns, face_size) f_display._centering = "face" f_display.update_source = True transform_image_mock = mocker.MagicMock(return_value=np.zeros((face_size, face_size, 3), dtype=np.uint8)) monkeypatch.setattr("tools.preview.viewer.transform_image", transform_image_mock) mats = np.random.random((columns, 2, 3)).astype(np.float32) f_display.source = [mocker.MagicMock() for _ in range(columns)] for idx, mock in enumerate(f_display.source): assert isinstance(mock, MagicMock) mock.inbound.detected_faces.__getitem__ = lambda self, x, y=mock: y mock.aligned.matrix = mats[idx] mock.inbound.filename = f"test_filename_{idx}.txt" f_display._crop_source_faces() assert len(f_display._faces.filenames) == columns assert len(f_display._faces.matrix) == columns assert len(f_display._faces.src) == columns assert not f_display.update_source assert transform_image_mock.call_count == columns for idx in range(columns): assert f_display._faces.filenames[idx] == f"test_filename_{idx}" assert np.all(f_display._faces.matrix[idx] == mats[idx]) @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test__crop_destination_faces(self, columns: int, face_size: int, monkeypatch: pytest.MonkeyPatch, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` _crop_destination_faces method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer mocker: :class:`pytest_mock.MockerFixture` Mocker for dummying in full frames """ f_display = self.get_faces_display_instance(columns, face_size) f_display._centering = "face" transform_image_mock = mocker.MagicMock(return_value=np.zeros((face_size, face_size, 3), dtype=np.uint8)) monkeypatch.setattr("tools.preview.viewer.transform_image", transform_image_mock) f_display.source = [mocker.MagicMock() for _ in range(columns)] for item in f_display.source: # type ignore item.inbound.image = np.random.rand(1280, 720, 3) # type:ignore f_display._crop_destination_faces() assert transform_image_mock.call_count == columns assert len(f_display._faces.dst) == columns assert all(f.shape == (face_size, face_size, 3) for f in f_display._faces.dst) @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test__header_text(self, columns: int, face_size: int, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` _header_text method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer mocker: :class:`pytest_mock.MockerFixture` Mocker for dummying in cv2 calls """ f_display = self.get_faces_display_instance(columns, face_size) f_display.source = [None for _ in range(columns)] # type:ignore f_display._faces.filenames = [f"filename_{idx}.png" for idx in range(columns)] cv2_mock = mocker.patch("tools.preview.viewer.cv2") text_width, text_height = (100, 32) cv2_mock.getTextSize.return_value = [(text_width, text_height), ] header_box = f_display._header_text() assert cv2_mock.getTextSize.call_count == columns assert cv2_mock.putText.call_count == columns assert header_box.shape == (face_size // 8, face_size * columns, 3) @pytest.mark.parametrize("columns, face_size", _PARAMS, ids=_IDS) def test__draw_rect_text(self, columns: int, face_size: int, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.FacesDisplay` _draw_rect method Parameters ---------- columns: int The number of columns to display in the viewer face_size: int The size of each face image to be displayed in the viewer mocker: :class:`pytest_mock.MockerFixture` Mocker for dummying in cv2 calls """ f_display = self.get_faces_display_instance(columns, face_size) cv2_mock = mocker.patch("tools.preview.viewer.cv2") image = (np.random.rand(face_size, face_size, 3) * 255.0) + 50 assert image.max() > 255.0 output = f_display._draw_rect(image) cv2_mock.rectangle.assert_called_once() assert output.max() == 255.0 # np.clip class TestImagesCanvas: """ Test :class:`~tools.preview.viewer.ImagesCanvas` """ @pytest.fixture def parent(self) -> MagicMock: """ Mock object to act as the parent widget to the ImagesCanvas Returns -------- :class:`unittest.mock.MagicMock` The mocked ttk.PanedWindow widget """ retval = MagicMock(spec=ttk.PanedWindow) retval.tk = retval retval._w = "mock_ttkPanedWindow" retval.children = {} retval.call = retval retval.createcommand = retval retval.preview_display = MagicMock(spec=FacesDisplay) return retval @pytest.fixture(name="images_canvas_instance") def images_canvas_fixture(self, parent) -> ImagesCanvas: """ Fixture for creating a testing :class:`~tools.preview.viewer.ImagesCanvas` instance Parameters ---------- parent: :class:`unittest.mock.MagicMock` The mocked ttk.PanedWindow parent Returns ------- :class:`~tools.preview.viewer.ImagesCanvas` The class instance for testing """ app = MagicMock() return ImagesCanvas(app, parent) def test_init(self, images_canvas_instance: ImagesCanvas, parent: MagicMock) -> None: """ Test :class:`~tools.preview.viewer.ImagesCanvas` __init__ method Parameters ---------- images_canvas_instance: :class:`~tools.preview.viewer.ImagesCanvas` The class instance to test parent: :class:`unittest.mock.MagicMock` The mocked parent ttk.PanedWindow """ assert images_canvas_instance._display == parent.preview_display assert isinstance(images_canvas_instance._canvas, tk.Canvas) assert images_canvas_instance._canvas.master == images_canvas_instance assert images_canvas_instance._canvas.winfo_ismapped() def test_resize(self, images_canvas_instance: ImagesCanvas, parent: MagicMock, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.ImagesCanvas` resize method Parameters ---------- images_canvas_instance: :class:`~tools.preview.viewer.ImagesCanvas` The class instance to test parent: :class:`unittest.mock.MagicMock` The mocked parent ttk.PanedWindow mocker: :class:`pytest_mock.MockerFixture` Mocker for dummying in tk calls """ event_mock = mocker.MagicMock(spec=tk.Event, width=100, height=200) images_canvas_instance.reload = T.cast(MagicMock, mocker.MagicMock()) # type:ignore images_canvas_instance._resize(event_mock) parent.preview_display.set_display_dimensions.assert_called_once_with((100, 200)) images_canvas_instance.reload.assert_called_once() def test_reload(self, images_canvas_instance: ImagesCanvas, parent: MagicMock, mocker: pytest_mock.MockerFixture) -> None: """ Test :class:`~tools.preview.viewer.ImagesCanvas` reload method Parameters ---------- images_canvas_instance: :class:`~tools.preview.viewer.ImagesCanvas` The class instance to test parent: :class:`unittest.mock.MagicMock` The mocked parent ttk.PanedWindow mocker: :class:`pytest_mock.MockerFixture` Mocker for dummying in tk calls """ itemconfig_mock = mocker.patch.object(tk.Canvas, "itemconfig") images_canvas_instance.reload() parent.preview_display.update_tk_image.assert_called_once() itemconfig_mock.assert_called_once()