code release

This commit is contained in:
iperov 2021-07-23 17:34:49 +04:00
parent b941ba41a3
commit a902f11f74
354 changed files with 826570 additions and 1 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
*
!*.py
!*.md
!*.txt
!*.jpg
!*.png
!requirements*

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2018 The Python Packaging Authority
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -37,7 +37,7 @@ You can train your own face model using <a href="https://github.com/iperov/DeepF
## Minimum system requirements
NVIDIA GTX 750 or higher (AMD is not supported)
NVIDIA GTX 750 or higher (AMD is not supported yet)
Modern CPU with AVX instructions

3982
__dev_archived/_trash.txt Normal file

File diff suppressed because it is too large Load diff

BIN
__dev_archived/archived.zip Normal file

Binary file not shown.

View file

@ -0,0 +1,309 @@
from pathlib import Path
from typing import List
from localization import L, Localization
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from resources.gfx import QXImageDB
from xlib import os as lib_os
from xlib import qt as lib_qt
from xlib.qt.widgets.QXLabel import QXLabel
from . import backend
from .ui.QCameraSource import QCameraSource
from .ui.QFaceAligner import QFaceAligner
from .ui.QFaceDetector import QFaceDetector
from .ui.QFaceMarker import QFaceMarker
from .ui.QFaceMerger import QFaceMerger
from .ui.QFaceSwapper import QFaceSwapper
from .ui.QFileSource import QFileSource
from .ui.QFrameAdjuster import QFrameAdjuster
from .ui.QStreamOutput import QStreamOutput
from .ui.widgets.QBCFaceAlignViewer import QBCFaceAlignViewer
from .ui.widgets.QBCFaceSwapViewer import QBCFaceSwapViewer
from .ui.widgets.QBCFinalFrameViewer import QBCFinalFrameViewer
from .ui.widgets.QBCFrameViewer import QBCFrameViewer
class QLiveSwap(lib_qt.QXWidget):
def __init__(self, userdata_path : Path,
settings_dirpath : Path):
super().__init__()
dfm_models_path = userdata_path / 'dfm_models'
dfm_models_path.mkdir(parents=True, exist_ok=True)
output_sequence_path = userdata_path / 'output_sequence'
output_sequence_path.mkdir(parents=True, exist_ok=True)
# Construct backend config
backend_db = self.backend_db = backend.BackendDB( settings_dirpath / 'states.dat' )
backed_weak_heap = self.backed_weak_heap = backend.BackendWeakHeap(size_mb=1024)
reemit_frame_signal = self.reemit_frame_signal = backend.BackendSignal()
multi_sources_bc_out = backend.BackendConnection(multi_producer=True)
face_detector_bc_out = backend.BackendConnection()
face_marker_bc_out = backend.BackendConnection()
face_aligner_bc_out = backend.BackendConnection()
face_swapper_bc_out = backend.BackendConnection()
frame_adjuster_bc_out = backend.BackendConnection()
face_merger_bc_out = backend.BackendConnection()
file_source = self.file_source = backend.FileSource (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_out=multi_sources_bc_out, backend_db=backend_db)
camera_source = self.camera_source = backend.CameraSource (weak_heap=backed_weak_heap, bc_out=multi_sources_bc_out, backend_db=backend_db)
face_detector = self.face_detector = backend.FaceDetector (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=multi_sources_bc_out, bc_out=face_detector_bc_out, backend_db=backend_db )
face_marker = self.face_marker = backend.FaceMarker (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_detector_bc_out, bc_out=face_marker_bc_out, backend_db=backend_db)
face_aligner = self.face_aligner = backend.FaceAligner (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_marker_bc_out, bc_out=face_aligner_bc_out, backend_db=backend_db )
face_swapper = self.face_swapper = backend.FaceSwapper (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_aligner_bc_out, bc_out=face_swapper_bc_out, dfm_models_path=dfm_models_path, backend_db=backend_db )
frame_adjuster = self.frame_adjuster = backend.FrameAdjuster(weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_swapper_bc_out, bc_out=frame_adjuster_bc_out, backend_db=backend_db )
face_merger = self.face_merger = backend.FaceMerger (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=frame_adjuster_bc_out, bc_out=face_merger_bc_out, backend_db=backend_db )
stream_output = self.stream_output = backend.StreamOutput (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_merger_bc_out, save_default_path=userdata_path, backend_db=backend_db)
self.all_backends : List[backend.BackendHost] = [file_source, camera_source, face_detector, face_marker, face_aligner, face_swapper, frame_adjuster, face_merger, stream_output]
self.q_file_source = QFileSource(self.file_source)
self.q_camera_source = QCameraSource(self.camera_source)
self.q_face_detector = QFaceDetector(self.face_detector)
self.q_face_marker = QFaceMarker(self.face_marker)
self.q_face_aligner = QFaceAligner(self.face_aligner)
self.q_face_swapper = QFaceSwapper(self.face_swapper, dfm_models_path=dfm_models_path)
self.q_frame_adjuster = QFrameAdjuster(self.frame_adjuster)
self.q_face_merger = QFaceMerger(self.face_merger)
self.q_stream_output = QStreamOutput(self.stream_output)
self.q_ds_frame_viewer = QBCFrameViewer(backed_weak_heap, multi_sources_bc_out)
self.q_ds_fa_viewer = QBCFaceAlignViewer(backed_weak_heap, face_aligner_bc_out, preview_width=256)
self.q_ds_fc_viewer = QBCFaceSwapViewer(backed_weak_heap, face_swapper_bc_out, preview_width=256)
self.q_ds_merged_frame_viewer = QBCFinalFrameViewer(backed_weak_heap, face_merger_bc_out)
q_nodes = lib_qt.QXWidget(size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed),
layout=lib_qt.QXHBoxLayout([
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_file_source, self.q_camera_source], spacing=5), fixed_width=256 ),
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_face_detector, self.q_face_aligner,], spacing=5), fixed_width=256),
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_face_marker, self.q_face_swapper], spacing=5), fixed_width=256),
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_frame_adjuster, self.q_face_merger, self.q_stream_output, ], spacing=5), fixed_width=256),
], spacing=5))
q_view_nodes = lib_qt.QXWidget(size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed),
layout=lib_qt.QXHBoxLayout([
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_frame_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_fa_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_fc_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_merged_frame_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
], spacing=5))
self.setLayout(lib_qt.QXVBoxLayout(
[ (lib_qt.QXWidget( layout=lib_qt.QXVBoxLayout([
(q_nodes, Qt.AlignmentFlag.AlignTop),
(q_view_nodes, Qt.AlignmentFlag.AlignHCenter),
], spacing=5)),
Qt.AlignmentFlag.AlignCenter),
]))
self._timer = lib_qt.QXTimer(interval=5, timeout=self._on_timer_5ms, start=True)
def _process_messages(self):
self.backend_db.process_messages()
for backend in self.all_backends:
backend.process_messages()
def _on_timer_5ms(self):
self._process_messages()
def clear_backend_db(self):
self.backend_db.clear()
def initialize(self):
for backend in self.all_backends:
backend.restore_on_off_state()
def finalize(self):
# Gracefully stop the backend
for backend in self.all_backends:
while backend.is_starting() or backend.is_stopping():
self._process_messages()
backend.save_on_off_state()
backend.stop()
while not all( x.is_stopped() for x in self.all_backends ):
self._process_messages()
self.backend_db.finish_pending_jobs()
self.q_ds_frame_viewer.clear()
self.q_ds_fa_viewer.clear()
class QDFLAppWindow(lib_qt.QXWindow):
def __init__(self, userdata_path, settings_dirpath):
super().__init__(save_load_state=True, size_policy=(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) )
self._userdata_path = userdata_path
self._settings_dirpath = settings_dirpath
menu_bar = lib_qt.QXMenuBar( font=QXFontDB.get_default_font(size=10), size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.MinimumExpanding) )
menu_file = menu_bar.addMenu( L('@QDFLAppWindow.file') )
menu_language = menu_bar.addMenu( L('@QDFLAppWindow.language') )
menu_file_action_reinitialize = menu_file.addAction( L('@QDFLAppWindow.reinitialize') )
menu_file_action_reinitialize.triggered.connect(lambda: lib_qt.QXMainApplication.get_singleton().reinitialize() )
menu_file_action_reset_settings = menu_file.addAction( L('@QDFLAppWindow.reset_modules_settings') )
menu_file_action_reset_settings.triggered.connect(self._on_reset_modules_settings)
menu_file_action_quit = menu_file.addAction( L('@QDFLAppWindow.quit') )
menu_file_action_quit.triggered.connect(lambda: lib_qt.QXMainApplication.quit() )
menu_language_action_english = menu_language.addAction('English' )
menu_language_action_english.triggered.connect(lambda: (lib_qt.QXMainApplication.get_singleton().set_language('en-US'), lib_qt.QXMainApplication.get_singleton().reinitialize()) )
menu_language_action_russian = menu_language.addAction('Русский')
menu_language_action_russian.triggered.connect(lambda: (lib_qt.QXMainApplication.get_singleton().set_language('ru-RU'), lib_qt.QXMainApplication.get_singleton().reinitialize()) )
menu_language_action_chinesse = menu_language.addAction('汉语')
menu_language_action_chinesse.triggered.connect(lambda: (lib_qt.QXMainApplication.get_singleton().set_language('zh-CN'), lib_qt.QXMainApplication.get_singleton().reinitialize()) )
menu_help = menu_bar.addMenu( L('@QDFLAppWindow.help') )
menu_help_action_github = menu_help.addAction( L('@QDFLAppWindow.visit_github_page') )
menu_help_action_github.triggered.connect(lambda: QDesktopServices.openUrl(QUrl('https://github.com/iperov/DeepFaceLive' )))
self.q_live_swap = None
self.q_live_swap_container = lib_qt.QXWidget()
self.content_l = lib_qt.QXVBoxLayout()
cb_process_priority = self._cb_process_priority = lib_qt.QXSaveableComboBox(
db_key = '_QDFLAppWindow_process_priority',
choices=[lib_os.ProcessPriority.NORMAL, lib_os.ProcessPriority.IDLE],
default_choice=lib_os.ProcessPriority.NORMAL,
choices_names=[ L('@QDFLAppWindow.process_priority.normal'), L('@QDFLAppWindow.process_priority.lowest') ],
on_choice_selected=self._on_cb_process_priority_choice)
menu_bar_tail = lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([
10,
QXLabel(text=L('@QDFLAppWindow.process_priority')),
4,
cb_process_priority,
]),
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
self.setLayout( lib_qt.QXVBoxLayout([ lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([menu_bar, menu_bar_tail, lib_qt.QXFrame() ]), size_policy=(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Fixed)),
5,
lib_qt.QXWidget(layout=self.content_l)
] ))
self.call_on_closeEvent(self._on_closeEvent)
q_live_swap = self.q_live_swap = QLiveSwap(userdata_path=self._userdata_path, settings_dirpath=self._settings_dirpath)
q_live_swap.initialize()
self.content_l.addWidget(q_live_swap)
def _on_reset_modules_settings(self):
if self.q_live_swap is not None:
self.q_live_swap.clear_backend_db()
lib_qt.QXMainApplication.get_singleton().reinitialize()
def _on_cb_process_priority_choice(self, prio : lib_os.ProcessPriority, _):
lib_os.set_process_priority(prio)
if self.q_live_swap is not None:
lib_qt.QXMainApplication.get_singleton().reinitialize()
def finalize(self):
self.q_live_swap.finalize()
def _on_closeEvent(self):
self.finalize()
class QSplashWindow(lib_qt.QXWindow):
def __init__(self, next_window=None):
super().__init__()
self.setWindowFlags(Qt.WindowType.SplashScreen)
logo_deepfacelive = lib_qt.QXLabel(image=QXImageDB.splash_deepfacelive())
self.setLayout( lib_qt.QXVBoxLayout([
( lib_qt.QXHBoxLayout([logo_deepfacelive]), Qt.AlignmentFlag.AlignCenter),
], spacing=0, contents_margins=20))
class DeepFaceLiveApp(lib_qt.QXMainApplication):
def __init__(self, userdata_path):
self.userdata_path = userdata_path
settings_dirpath = self.settings_dirpath = userdata_path / 'settings'
if not settings_dirpath.exists():
settings_dirpath.mkdir(parents=True)
super().__init__(app_name='DeepFaceLive', settings_dirpath=settings_dirpath)
self.setFont( QXFontDB.get_default_font() )
self.setWindowIcon( QXImageDB.app_icon().as_QIcon() )
splash_wnd = self.splash_wnd = QSplashWindow()
splash_wnd.show()
splash_wnd.center_on_screen()
self._dfl_wnd = None
self.initialize()
self._t = lib_qt.QXTimer(interval=1666, timeout=self._on_splash_wnd_expired, single_shot=True, start=True)
def on_reinitialize(self):
self.finalize()
import gc
gc.collect()
gc.collect()
self.initialize()
self._dfl_wnd.show()
def initialize(self):
Localization.set_language( self.get_language() )
if self._dfl_wnd is None:
self._dfl_wnd = QDFLAppWindow(userdata_path=self.userdata_path, settings_dirpath=self.settings_dirpath)
def finalize(self):
if self._dfl_wnd is not None:
self._dfl_wnd.close()
self._dfl_wnd.deleteLater()
self._dfl_wnd = None
def _on_splash_wnd_expired(self):
self._dfl_wnd.show()
if self.splash_wnd is not None:
self.splash_wnd.hide()
self.splash_wnd.deleteLater()
self.splash_wnd = None
#import gc
#gc.collect()
#gc.collect()
# def _reinitialize(self, init_new=True):
# if self.q_live_swap is not None:
# self.content_l.removeWidget(self.q_live_swap)
# self.q_live_swap.finalize()
# self.q_live_swap.deleteLater()
# self.q_live_swap = None
# import gc
# gc.collect()
# gc.collect()
# LStrings.initialize()
# if init_new:
# self.q_live_swap = QLiveSwap(userdata_path=self._userdata_path, settings_dirpath=self._settings_dirpath)
# self.q_live_swap.initialize()
# self.content_l.addWidget(self.q_live_swap)
#self._reinitialize(init_new=False)

View file

@ -0,0 +1,200 @@
import multiprocessing
import pickle
from typing import List, Union
import numpy as np
from xlib import mp as lib_mp
from xlib import time as lib_time
from xlib.facemeta import FaceMark
from xlib.mp import csw as lib_csw
from xlib.python.EventListener import EventListener
class BackendConnectionData:
"""
data class for BackendConnection
Should not contain large buffers.
Large buffers are stored via MPWeakHeap
"""
def __init__(self, uid ):
super().__init__()
self._weak_heap = None
self._weak_heap_refs = {}
self._weak_heap_image_infos = {}
self._errors = []
self._uid = uid
self._is_frame_reemitted = None
self._frame_name = None
self._frame_count = None
self._frame_num = None
self._frame_fps = None
self._frame_timestamp = None
self._merged_frame_name = None
self._face_mark_list = []
def __getstate__(self, ):
d = self.__dict__.copy()
d['_weak_heap'] = None
return d
def assign_weak_heap(self, weak_heap : lib_mp.MPWeakHeap):
self._weak_heap = weak_heap
def set_file(self, name : str, data : Union[bytes, bytearray, memoryview]):
self._weak_heap_refs[name] = self._weak_heap.add_data(data)
def get_file(self, name : str) -> Union[bytes, None]:
ref = self._weak_heap_refs.get(name, None)
if ref is not None:
return self._weak_heap.get_data(ref)
return None
def set_image(self, name : str, image : np.ndarray):
"""
store image to weak heap
name str
image np.ndarray
"""
self.set_file(name, image.data)
self._weak_heap_image_infos[name] = (image.shape, image.dtype)
def get_image(self, name : str) -> Union[np.ndarray, None]:
if name is None:
return None
image_info = self._weak_heap_image_infos.get(name, None)
buffer = self.get_file(name)
if image_info is not None and buffer is not None:
shape, dtype = image_info
return np.ndarray(shape, dtype=dtype, buffer=buffer)
return None
def add_error(self, err : str):
self._errors.append(err)
def get_uid(self) -> int: return self._uid
def get_is_frame_reemitted(self) -> Union[bool, None]: return self._is_frame_reemitted
def set_is_frame_reemitted(self, is_frame_reemitted : bool): self._is_frame_reemitted = is_frame_reemitted
def get_frame_name(self) -> Union[str, None]: return self._frame_name
def set_frame_name(self, frame_name : str): self._frame_name = frame_name
def get_frame_count(self) -> Union[int, None]: return self._frame_count
def set_frame_count(self, frame_count : int): self._frame_count = frame_count
def get_frame_num(self) -> Union[int, None]: return self._frame_num
def set_frame_num(self, frame_num : int): self._frame_num = frame_num
def get_frame_fps(self) -> Union[float, None]: return self._frame_fps
def set_frame_fps(self, frame_fps : float): self._frame_fps = frame_fps
def get_frame_timestamp(self) -> Union[float, None]: return self._frame_timestamp
def set_frame_timestamp(self, frame_timestamp : float): self._frame_timestamp = frame_timestamp
def get_merged_frame_name(self) -> Union[str, None]: return self._merged_frame_name
def set_merged_frame_name(self, merged_frame_name : str): self._merged_frame_name = merged_frame_name
def get_face_mark_list(self) -> List[FaceMark]: return self._face_mark_list
def add_face_mark(self, face_mark : FaceMark):
if not isinstance(face_mark, FaceMark):
raise ValueError(f'face_mark must be an instance of FaceMark')
self._face_mark_list.append(face_mark)
class BackendConnection:
def __init__(self, multi_producer=False):
self._rd = lib_mp.MPSPSCMRRingData(table_size=8192, heap_size_mb=8, multi_producer=multi_producer)
def write(self, bcd : BackendConnectionData):
self._rd.write( pickle.dumps(bcd) )
def read(self, timeout : float = 0) -> Union[BackendConnectionData, None]:
b = self._rd.read(timeout=timeout)
if b is not None:
return pickle.loads(b)
return None
def get_write_id(self) -> int:
return self._rd.get_write_id()
def get_by_id(self, id) -> Union[BackendConnectionData, None]:
b = self._rd.get_by_id(id)
if b is not None:
return pickle.loads(b)
return None
def wait_for_read(self, timeout : float) -> bool:
"""
returns True if ready to .read()
"""
return self._rd.wait_for_read(timeout)
def is_full_read(self, buffer_size=0) -> bool:
"""
if fully readed by receiver side minus buffer_size
"""
return self._rd.get_read_id() == self._rd.get_write_id()
class BackendSignal:
def __init__(self):
self._ev = multiprocessing.Event()
def send(self):
self._ev.set()
def recv(self):
is_set = self._ev.is_set()
if is_set:
self._ev.clear()
return is_set
class BackendWeakHeap(lib_mp.MPWeakHeap):
...
class BackendDB(lib_csw.DB):
...
class BackendWorkerState(lib_csw.WorkerState):
...
class BackendHost(lib_csw.Host):
def __init__(self, backend_db : BackendDB = None,
sheet_cls = None,
worker_cls = None,
worker_state_cls : BackendWorkerState = None,
worker_start_args = None,
worker_start_kwargs = None):
super().__init__(db=backend_db,
sheet_cls = sheet_cls,
worker_cls = worker_cls,
worker_state_cls = worker_state_cls,
worker_start_args = worker_start_args,
worker_start_kwargs = worker_start_kwargs)
self._profile_timing_evl = EventListener()
self.call_on_msg('_profile_timing', self._on_profile_timing_msg)
def _on_profile_timing_msg(self, timing : float):
self._profile_timing_evl.call(timing)
def call_on_profile_timing(self, func_or_list):
self._profile_timing_evl.add(func_or_list)
class BackendWorker(lib_csw.Worker):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._profile_timing_measurer = lib_time.AverageMeasurer(samples=120)
def start_profile_timing(self):
self._profile_timing_measurer.start()
def stop_profile_timing(self):
self.send_msg('_profile_timing', self._profile_timing_measurer.stop() )

View file

@ -0,0 +1,334 @@
import platform
import time
from datetime import datetime
from enum import IntEnum
from typing import List
import cv2
import numpy as np
from xlib import os as lib_os
from xlib.image import ImageProcessor
from xlib.mp import csw as lib_csw
from .BackendBase import (BackendConnection, BackendConnectionData, BackendDB,
BackendHost, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class CameraSource(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, bc_out : BackendConnection, backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=CameraSourceWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, bc_out] )
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class _ResolutionType(IntEnum):
RES_320x240 = 0
RES_640x480 = 1
RES_720x480 = 2
RES_1280x720 = 3
RES_1280x960 = 4
RES_1366x768 = 5
RES_1920x1080 = 6
_ResolutionType_names = {_ResolutionType.RES_320x240 : '320x240',
_ResolutionType.RES_640x480 : '640x480',
_ResolutionType.RES_720x480 : '720x480',
_ResolutionType.RES_1280x720 : '1280x720',
_ResolutionType.RES_1280x960 : '1280x960',
_ResolutionType.RES_1366x768 : '1366x768',
_ResolutionType.RES_1920x1080 : '1920x1080',
}
_ResolutionType_wh = {_ResolutionType.RES_320x240: (320,240),
_ResolutionType.RES_640x480: (640,480),
_ResolutionType.RES_720x480: (720,480),
_ResolutionType.RES_1280x720: (1280,720),
_ResolutionType.RES_1280x960: (1280,960),
_ResolutionType.RES_1366x768: (1366,768),
_ResolutionType.RES_1920x1080: (1920,1080),
}
class _DriverType(IntEnum):
DSHOW = 0
MSMF = 1
GSTREAMER = 2
_DriverType_names = { _DriverType.DSHOW : 'DirectShow',
_DriverType.MSMF : 'Microsoft Media Foundation',
_DriverType.GSTREAMER : 'GStreamer',
}
class _RotationType(IntEnum):
ROTATION_0 = 0
ROTATION_90 = 1
ROTATION_180 = 2
ROTATION_270 = 3
_RotationType_names = ['0 degrees', '90 degrees', '180 degrees', '270 degrees']
class CameraSourceWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, bc_out : BackendConnection):
self.weak_heap = weak_heap
self.bc_out = bc_out
self.bcd_uid = 0
self.pending_bcd = None
self.vcap = None
self.last_timestamp = 0
lib_os.set_timer_resolution(4)
state, cs = self.get_state(), self.get_control_sheet()
cs.device_idx.call_on_selected(self.on_cs_device_idx_selected)
cs.driver.call_on_selected(self.on_cs_driver_selected)
cs.resolution.call_on_selected(self.on_cs_resolution_selected)
cs.fps.call_on_number(self.on_cs_fps)
cs.rotation.call_on_selected(self.on_cs_rotation_selected)
cs.flip_horizontal.call_on_flag(self.on_cs_flip_horizontal)
cs.open_settings.call_on_signal(self.on_cs_open_settings)
cs.load_settings.call_on_signal(self.on_cs_load_settings)
cs.save_settings.call_on_signal(self.on_cs_save_settings)
cs.device_idx.enable()
cs.device_idx.set_choices([0,1,2,3], none_choice_name='@misc.menu_select')
cs.device_idx.select(state.device_idx)
cs.driver.enable()
cs.driver.set_choices(_DriverType, _DriverType_names, none_choice_name='@misc.menu_select')
cs.driver.select(state.driver)
cs.resolution.enable()
cs.resolution.set_choices(_ResolutionType, _ResolutionType_names, none_choice_name=None)
cs.resolution.select(state.resolution if state.resolution is not None else _ResolutionType.RES_640x480)
if state.device_idx is not None and \
state.driver is not None:
cv_api = {_DriverType.DSHOW: cv2.CAP_DSHOW,
_DriverType.MSMF: cv2.CAP_MSMF,
_DriverType.GSTREAMER: cv2.CAP_GSTREAMER,
}[state.driver]
vcap = cv2.VideoCapture(state.device_idx, cv_api)
if vcap.isOpened():
self.vcap = vcap
w, h = _ResolutionType_wh[state.resolution]
vcap.set(cv2.CAP_PROP_FRAME_WIDTH, w)
vcap.set(cv2.CAP_PROP_FRAME_HEIGHT, h)
if vcap.isOpened():
cs.fps.enable()
cs.fps.set_config(lib_csw.Number.Config(min=0, max=240, step=1.0, decimals=2, zero_is_auto=True, allow_instant_update=False))
cs.fps.set_number(state.fps if state.fps is not None else 0)
cs.rotation.enable()
cs.rotation.set_choices(_RotationType, _RotationType_names, none_choice_name=None)
cs.rotation.select(state.rotation if state.rotation is not None else _RotationType.ROTATION_0)
cs.flip_horizontal.enable()
cs.flip_horizontal.set_flag(state.flip_horizontal if state.flip_horizontal is not None else False)
if platform.system() == 'Windows':
cs.open_settings.enable()
cs.load_settings.enable()
cs.save_settings.enable()
else:
cs.device_idx.unselect()
cs.driver.unselect()
def on_cs_device_idx_selected(self, idx, device_idx):
cs, state = self.get_control_sheet(), self.get_state()
if state.device_idx != device_idx:
state.device_idx = device_idx
self.save_state()
if self.is_started():
self.restart()
def on_cs_driver_selected(self, idx, driver):
cs, state = self.get_control_sheet(), self.get_state()
if state.driver != driver:
state.driver = driver
self.save_state()
if self.is_started():
self.restart()
def on_cs_resolution_selected(self, idx, resolution : _ResolutionType):
state, cs = self.get_state(), self.get_control_sheet()
if state.resolution != resolution:
state.resolution = resolution
self.save_state()
if self.is_started():
self.restart()
def on_cs_fps(self, fps):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.fps.get_config()
fps = state.fps = np.clip(fps, cfg.min, cfg.max)
cs.fps.set_number(fps)
self.save_state()
def on_cs_rotation_selected(self, idx, _rot_type : _RotationType):
cs, state = self.get_control_sheet(), self.get_state()
state.rotation = _rot_type
self.save_state()
def on_cs_flip_horizontal(self, flip_horizontal):
state, cs = self.get_state(), self.get_control_sheet()
state.flip_horizontal = flip_horizontal
self.save_state()
def on_cs_open_settings(self):
cs, state = self.get_control_sheet(), self.get_state()
if self.vcap is not None and self.vcap.isOpened():
self.vcap.set(cv2.CAP_PROP_SETTINGS, 0)
def on_cs_load_settings(self):
cs, state = self.get_control_sheet(), self.get_state()
vcap = self.vcap
if vcap is not None:
settings = state.settings_by_idx.get(state.device_idx, None)
if settings is not None:
for setting_name, value in settings.items():
setting_id = getattr(cv2, setting_name, None)
if setting_id is not None:
vcap.set(setting_id, value)
def on_cs_save_settings(self):
cs, state = self.get_control_sheet(), self.get_state()
vcap = self.vcap
if vcap is not None:
settings = {}
for setting_name in self._get_vcap_setting_name_list():
setting_id = getattr(cv2, setting_name, None)
if setting_id is not None:
settings[setting_name] = vcap.get(setting_id)
state.settings_by_idx[state.device_idx] = settings
self.save_state()
def on_tick(self):
if self.vcap is not None and not self.vcap.isOpened():
self.set_vcap(None)
if self.vcap is not None:
state, cs = self.get_state(), self.get_control_sheet()
self.start_profile_timing()
ret, img = self.vcap.read()
if ret:
timestamp = datetime.now().timestamp()
fps = state.fps
if fps == 0 or ((timestamp - self.last_timestamp) > 1.0 / fps):
self.last_timestamp = timestamp
ip = ImageProcessor(img)
#if state.target_width != 0:
# ip.fit_in(TW=state.target_width)
ip.ch(3).to_uint8()
rotation = state.rotation
if rotation == _RotationType.ROTATION_90:
ip.rotate90()
elif rotation == _RotationType.ROTATION_180:
ip.rotate180()
elif rotation == _RotationType.ROTATION_270:
ip.rotate270()
if state.flip_horizontal:
ip.flip_horizontal()
img = ip.get_image('HWC')
bcd_uid = self.bcd_uid = self.bcd_uid + 1
bcd = BackendConnectionData(uid=bcd_uid)
bcd.assign_weak_heap(self.weak_heap)
frame_name = f'Camera_{state.device_idx}_{bcd_uid:06}'
bcd.set_frame_name(frame_name)
bcd.set_frame_num(bcd_uid)
bcd.set_frame_timestamp(timestamp)
bcd.set_image(frame_name, img)
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
time.sleep(0.001)
def set_vcap(self, vcap):
if self.vcap is not None:
if self.vcap.isOpened():
self.vcap.release()
self.vcap = None
self.vcap = vcap
def on_stop(self):
if self.vcap is not None:
if self.vcap.isOpened():
self.vcap.release()
self.vcap = None
def _get_vcap_setting_name_list(self) -> List[str]:
return ['CAP_PROP_BRIGHTNESS',
'CAP_PROP_CONTRAST',
'CAP_PROP_SATURATION',
'CAP_PROP_HUE',
'CAP_PROP_SHARPNESS',
'CAP_PROP_GAMMA',
'CAP_PROP_AUTO_WB',
'CAP_PROP_XI_AUTO_WB',
'CAP_PROP_XI_MANUAL_WB',
'CAP_PROP_WB_TEMPERATURE',
'CAP_PROP_BACKLIGHT',
'CAP_PROP_GAIN',
'CAP_PROP_AUTO_EXPOSURE',
'CAP_PROP_EXPOSURE']
class WorkerState(BackendWorkerState):
def __init__(self):
self.device_idx : int = None
self.driver : _DriverType = None
self.resolution : _ResolutionType = None
self.fps : float = None
self.rotation : _RotationType = None
self.flip_horizontal : bool = None
self.settings_by_idx = {}
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.device_idx = lib_csw.DynamicSingleSwitch.Client()
self.driver = lib_csw.DynamicSingleSwitch.Client()
self.resolution = lib_csw.DynamicSingleSwitch.Client()
self.fps = lib_csw.Number.Client()
self.rotation = lib_csw.DynamicSingleSwitch.Client()
self.flip_horizontal = lib_csw.Flag.Client()
self.open_settings = lib_csw.Signal.Client()
self.save_settings = lib_csw.Signal.Client()
self.load_settings = lib_csw.Signal.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.device_idx = lib_csw.DynamicSingleSwitch.Host()
self.driver = lib_csw.DynamicSingleSwitch.Host()
self.resolution = lib_csw.DynamicSingleSwitch.Host()
self.fps = lib_csw.Number.Host()
self.rotation = lib_csw.DynamicSingleSwitch.Host()
self.flip_horizontal = lib_csw.Flag.Host()
self.open_settings = lib_csw.Signal.Host()
self.save_settings = lib_csw.Signal.Host()
self.load_settings = lib_csw.Signal.Host()

View file

@ -0,0 +1,140 @@
import time
import numpy as np
from xlib import os as lib_os
from xlib.facemeta import FaceAlign, FaceULandmarks
from xlib.mp import csw as lib_csw
from xlib.python import all_is_not_None
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class FaceAligner(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FaceAlignerWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out])
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class FaceAlignerWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.bc_out = bc_out
self.pending_bcd = None
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.face_coverage.call_on_number(self.on_cs_face_coverage)
cs.resolution.call_on_number(self.on_cs_resolution)
cs.exclude_moving_parts.call_on_flag(self.on_cs_exclude_moving_parts)
cs.face_coverage.enable()
cs.face_coverage.set_config(lib_csw.Number.Config(min=0.1, max=4.0, step=0.1, decimals=1, allow_instant_update=True))
cs.face_coverage.set_number(state.face_coverage if state.face_coverage is not None else 2.2)
cs.resolution.enable()
cs.resolution.set_config(lib_csw.Number.Config(min=16, max=1024, step=16, decimals=0, allow_instant_update=True))
cs.resolution.set_number(state.resolution if state.resolution is not None else 224)
cs.exclude_moving_parts.enable()
cs.exclude_moving_parts.set_flag(state.exclude_moving_parts if state.exclude_moving_parts is not None else True)
def on_cs_face_coverage(self, face_coverage):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.face_coverage.get_config()
face_coverage = state.face_coverage = np.clip(face_coverage, cfg.min, cfg.max)
cs.face_coverage.set_number(face_coverage)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_resolution(self, resolution):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.resolution.get_config()
resolution = state.resolution = int(np.clip( (resolution//16)*16, cfg.min, cfg.max))
cs.resolution.set_number(resolution)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_exclude_moving_parts(self, exclude_moving_parts):
state, cs = self.get_state(), self.get_control_sheet()
state.exclude_moving_parts = exclude_moving_parts
self.save_state()
self.reemit_frame_signal.send()
def on_tick(self):
state, cs = self.get_state(), self.get_control_sheet()
if self.pending_bcd is None:
self.start_profile_timing()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
frame_name = bcd.get_frame_name()
frame_image = bcd.get_image(frame_name)
if all_is_not_None(state.face_coverage, state.resolution, frame_name, frame_image):
for face_id,face_mark in enumerate( bcd.get_face_mark_list() ):
face_ulmrks = face_mark.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_468)
if face_ulmrks is None:
face_ulmrks = face_mark.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_68)
if face_ulmrks is not None:
face_image, uni_mat = face_ulmrks.cut(frame_image, state.face_coverage, state.resolution, exclude_moving_parts=state.exclude_moving_parts)
face_align_image_name = f'{frame_name}_{face_id}_aligned'
face_align = FaceAlign()
face_align.set_image_name(face_align_image_name)
face_align.set_coverage(state.face_coverage)
face_align.set_source_face_ulandmarks_type(face_ulmrks.get_type())
face_align.set_source_to_aligned_uni_mat(uni_mat)
for face_ulmrks in face_mark.get_face_ulandmarks_list():
face_align.add_face_ulandmarks( face_ulmrks.transform(uni_mat) )
face_mark.set_face_align(face_align)
bcd.set_image(face_align_image_name, face_image)
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
else:
time.sleep(0.001)
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.face_coverage = lib_csw.Number.Client()
self.resolution = lib_csw.Number.Client()
self.exclude_moving_parts = lib_csw.Flag.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.face_coverage = lib_csw.Number.Host()
self.resolution = lib_csw.Number.Host()
self.exclude_moving_parts = lib_csw.Flag.Host()
class WorkerState(BackendWorkerState):
face_coverage : float = None
resolution : int = None
exclude_moving_parts : bool = None

View file

@ -0,0 +1,326 @@
import time
from enum import IntEnum
import numpy as np
from modelhub import onnx as onnx_models
from xlib import os as lib_os
from xlib.facemeta import FaceMark, FaceURect
from xlib.image import ImageProcessor
from xlib.mp import csw as lib_csw
from xlib.python import all_is_not_None
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class DetectorType(IntEnum):
CENTER_FACE = 0
S3FD = 1
YOLOV5 = 2
DetectorTypeNames = ['CenterFace', 'S3FD', 'YoloV5']
class FaceSortBy(IntEnum):
LARGEST = 0
DIST_FROM_CENTER = 1
FaceSortByNames = ['@FaceDetector.largest', '@FaceDetector.dist_from_center']
class FaceDetector(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap,
reemit_frame_signal : BackendSignal,
bc_in : BackendConnection,
bc_out : BackendConnection,
backend_db : BackendDB = None):
self._weak_heap = weak_heap
self._bc_out = bc_out
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FaceDetectorWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out] )
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
def get_weak_heap(self) -> BackendWeakHeap: return self._weak_heap
def get_bc_out(self) -> BackendConnection: return self._bc_out
class FaceDetectorWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal,
bc_in : BackendConnection,
bc_out : BackendConnection):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.bc_out = bc_out
self.pending_bcd = None
self.temporal_rects = []
self.CenterFace = None
self.S3FD = None
self.YoloV5Face = None
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.detector_type.call_on_selected(self.on_cs_detector_type)
cs.device.call_on_selected(self.on_cs_devices)
cs.fixed_window_size.call_on_number(self.on_cs_fixed_window_size)
cs.threshold.call_on_number(self.on_cs_threshold)
cs.max_faces.call_on_number(self.on_cs_max_faces)
cs.sort_by.call_on_selected(self.on_cs_sort_by)
cs.temporal_smoothing.call_on_number(self.on_cs_temporal_smoothing)
cs.detector_type.enable()
cs.detector_type.set_choices(DetectorType, DetectorTypeNames, none_choice_name='@misc.menu_select')
cs.detector_type.select(state.detector_type)
def on_cs_detector_type(self, idx, detector_type):
state, cs = self.get_state(), self.get_control_sheet()
if state.detector_type == detector_type:
if detector_type == DetectorType.CENTER_FACE:
cs.device.enable()
cs.device.set_choices( onnx_models.CenterFace.get_available_devices(), none_choice_name='@misc.menu_select')
cs.device.select(state.center_face_state.device)
elif detector_type == DetectorType.S3FD:
cs.device.enable()
cs.device.set_choices (onnx_models.S3FD.get_available_devices(), none_choice_name='@misc.menu_select')
cs.device.select(state.S3FD_state.device)
elif detector_type == DetectorType.YOLOV5:
cs.device.enable()
cs.device.set_choices (onnx_models.YoloV5Face.get_available_devices(), none_choice_name='@misc.menu_select')
cs.device.select(state.YoloV5_state.device)
else:
state.detector_type = detector_type
self.save_state()
self.restart()
def on_cs_devices(self, idx, device):
state, cs = self.get_state(), self.get_control_sheet()
detector_type = state.detector_type
if device is not None and \
(detector_type == DetectorType.CENTER_FACE and state.center_face_state.device == device) or \
(detector_type == DetectorType.S3FD and state.S3FD_state.device == device) or \
(detector_type == DetectorType.YOLOV5 and state.YoloV5_state.device == device):
detector_state = state.get_detector_state()
cs.max_faces.enable()
cs.max_faces.set_config(lib_csw.Number.Config(min=0, max=16, step=1, decimals=0, allow_instant_update=True))
cs.max_faces.set_number(detector_state.max_faces or 1)
if detector_type in [DetectorType.CENTER_FACE, DetectorType.S3FD, DetectorType.YOLOV5]:
cs.fixed_window_size.enable()
cs.fixed_window_size.set_config(lib_csw.Number.Config(min=0, max=4096, step=32, decimals=0, zero_is_auto=True, allow_instant_update=True))
cs.fixed_window_size.set_number(detector_state.fixed_window_size or 0)
cs.threshold.enable()
cs.threshold.set_config(lib_csw.Number.Config(min=0.01, max=1.0, step=0.01, decimals=2, allow_instant_update=True))
cs.threshold.set_number(detector_state.threshold or 0.5)
cs.sort_by.enable()
cs.sort_by.set_choices(FaceSortBy, FaceSortByNames)
cs.sort_by.select(detector_state.sort_by if detector_state.sort_by is not None else FaceSortBy.LARGEST)
cs.temporal_smoothing.enable()
cs.temporal_smoothing.set_config(lib_csw.Number.Config(min=1, max=10, step=1, allow_instant_update=True))
cs.temporal_smoothing.set_number(detector_state.temporal_smoothing or 1)
if detector_type == DetectorType.CENTER_FACE:
self.CenterFace = onnx_models.CenterFace(device)
elif detector_type == DetectorType.S3FD:
self.S3FD = onnx_models.S3FD(device)
elif detector_type == DetectorType.YOLOV5:
self.YoloV5Face = onnx_models.YoloV5Face(device)
else:
if detector_type == DetectorType.CENTER_FACE:
state.center_face_state.device = device
elif detector_type == DetectorType.S3FD:
state.S3FD_state.device = device
elif detector_type == DetectorType.YOLOV5:
state.YoloV5_state.device = device
self.save_state()
self.restart()
def on_cs_fixed_window_size(self, fixed_window_size):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.fixed_window_size.get_config()
fixed_window_size = state.get_detector_state().fixed_window_size = int(np.clip(fixed_window_size, cfg.min, cfg.max))
cs.fixed_window_size.set_number(fixed_window_size)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_threshold(self, threshold):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.threshold.get_config()
threshold = state.get_detector_state().threshold = np.clip(threshold, cfg.min, cfg.max)
cs.threshold.set_number(threshold)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_max_faces(self, max_faces):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.max_faces.get_config()
max_faces = state.get_detector_state().max_faces = np.clip(max_faces, cfg.min, cfg.max)
cs.max_faces.set_number(max_faces)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_sort_by(self, idx, sort_by):
state, cs = self.get_state(), self.get_control_sheet()
state.get_detector_state().sort_by = sort_by
self.save_state()
self.reemit_frame_signal.send()
def on_cs_temporal_smoothing(self, temporal_smoothing):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.temporal_smoothing.get_config()
temporal_smoothing = state.get_detector_state().temporal_smoothing = int(np.clip(temporal_smoothing, cfg.min, cfg.max))
if temporal_smoothing == 1:
self.temporal_rects = []
cs.temporal_smoothing.set_number(temporal_smoothing)
self.save_state()
self.reemit_frame_signal.send()
def on_tick(self):
state, cs = self.get_state(), self.get_control_sheet()
if self.pending_bcd is None:
self.start_profile_timing()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
is_frame_reemitted = bcd.get_is_frame_reemitted()
detector_type = state.detector_type
if (detector_type == DetectorType.CENTER_FACE and self.CenterFace is not None) or \
(detector_type == DetectorType.S3FD and self.S3FD is not None) or \
(detector_type == DetectorType.YOLOV5 and self.YoloV5Face is not None):
detector_state = state.get_detector_state()
frame_name = bcd.get_frame_name()
frame_image = bcd.get_image(frame_name)
if all_is_not_None(frame_image, frame_name):
_,H,W,_ = ImageProcessor(frame_image).get_dims()
rects = []
if detector_type == DetectorType.CENTER_FACE:
rects = self.CenterFace.extract (frame_image, threshold=detector_state.threshold, fixed_window=detector_state.fixed_window_size)[0]
elif detector_type == DetectorType.S3FD:
rects = self.S3FD.extract (frame_image, threshold=detector_state.threshold, fixed_window=detector_state.fixed_window_size)[0]
elif detector_type == DetectorType.YOLOV5:
rects = self.YoloV5Face.extract (frame_image, threshold=detector_state.threshold, fixed_window=detector_state.fixed_window_size)[0]
# to list of FaceURect
rects = [ FaceURect.from_ltrb( (l/W, t/H, r/W, b/H) ) for l,t,r,b in rects ]
# sort
if detector_state.sort_by == FaceSortBy.LARGEST:
rects = FaceURect.sort_by_area_size(rects)
elif detector_state.sort_by == FaceSortBy.DIST_FROM_CENTER:
rects = FaceURect.sort_by_dist_from_center(rects)
if len(rects) != 0:
max_faces = detector_state.max_faces
if max_faces != 0 and len(rects) > max_faces:
rects = rects[:max_faces]
if detector_state.temporal_smoothing != 1:
if len(self.temporal_rects) != len(rects):
self.temporal_rects = [ [] for _ in range(len(rects)) ]
for face_id, face_rect in enumerate(rects):
if detector_state.temporal_smoothing != 1:
if not is_frame_reemitted or len(self.temporal_rects[face_id]) == 0:
self.temporal_rects[face_id].append( face_rect.as_4pts() )
self.temporal_rects[face_id] = self.temporal_rects[face_id][-detector_state.temporal_smoothing:]
face_rect = FaceURect.from_4pts ( np.mean(self.temporal_rects[face_id],0 ) )
if face_rect.get_area() != 0:
face_mark = FaceMark()
face_mark.set_image_name(frame_name)
face_mark.set_face_urect ( face_rect )
bcd.add_face_mark(face_mark)
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
else:
time.sleep(0.001)
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.detector_type = lib_csw.DynamicSingleSwitch.Client()
self.device = lib_csw.DynamicSingleSwitch.Client()
self.sort_by = lib_csw.DynamicSingleSwitch.Client()
self.fixed_window_size = lib_csw.Number.Client()
self.threshold = lib_csw.Number.Client()
self.max_faces = lib_csw.Number.Client()
self.temporal_smoothing = lib_csw.Number.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.detector_type = lib_csw.DynamicSingleSwitch.Host()
self.device = lib_csw.DynamicSingleSwitch.Host()
self.sort_by = lib_csw.DynamicSingleSwitch.Host()
self.fixed_window_size = lib_csw.Number.Host()
self.threshold = lib_csw.Number.Host()
self.max_faces = lib_csw.Number.Host()
self.temporal_smoothing = lib_csw.Number.Host()
class DetectorState(BackendWorkerState):
fixed_window_size : int = None
threshold : float = None
max_faces : int = None
sort_by : FaceSortBy = None
temporal_smoothing : int = None
class CenterFaceState(BackendWorkerState):
device = None
class S3FDState(BackendWorkerState):
device = None
class YoloV5FaceState(BackendWorkerState):
device = None
class WorkerState(BackendWorkerState):
def __init__(self):
self.detector_type : DetectorType = None
self.detector_state = {}
self.center_face_state = CenterFaceState()
self.S3FD_state = S3FDState()
self.YoloV5_state = YoloV5FaceState()
def get_detector_state(self) -> DetectorState:
state = self.detector_state.get(self.detector_type, None)
if state is None:
state = self.detector_state[self.detector_type] = DetectorState()
return state

View file

@ -0,0 +1,299 @@
import time
from enum import IntEnum
import numpy as np
from modelhub import onnx as onnx_models
from xlib import cv as lib_cv
from xlib import os as lib_os
from xlib.facemeta import FaceULandmarks
from xlib.image import ImageProcessor
from xlib.mp import csw as lib_csw
from xlib.python import all_is_not_None
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class MarkerType(IntEnum):
OPENCV_LBF = 0
GOOGLE_FACEMESH = 1
MarkerTypeNames = ['OpenCV LBF','Google FaceMesh']
class FaceMarker(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FaceMarkerWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out, ] )
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class FaceMarkerWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal,
bc_in : BackendConnection,
bc_out : BackendConnection,
):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.bc_out = bc_out
self.pending_bcd = None
self.opencv_lbf = None
self.google_facemesh = None
self.temporal_lmrks = []
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.marker_type.call_on_selected(self.on_cs_marker_type)
cs.device.call_on_selected(self.on_cs_devices)
cs.marker_coverage.call_on_number(self.on_cs_marker_coverage)
cs.temporal_smoothing.call_on_number(self.on_cs_temporal_smoothing)
cs.marker_type.enable()
cs.marker_type.set_choices(MarkerType, MarkerTypeNames, none_choice_name='@misc.menu_select')
cs.marker_type.select(state.marker_type)
def on_cs_marker_type(self, idx, marker_type):
state, cs = self.get_state(), self.get_control_sheet()
if state.marker_type == marker_type:
cs.device.enable()
if marker_type == MarkerType.OPENCV_LBF:
cs.device.set_choices(['CPU'], none_choice_name='@misc.menu_select')
cs.device.select(state.opencv_lbf_state.device)
elif marker_type == MarkerType.GOOGLE_FACEMESH:
cs.device.set_choices(onnx_models.FaceMesh.get_available_devices(), none_choice_name='@misc.menu_select')
cs.device.select(state.google_facemesh_state.device)
else:
state.marker_type = marker_type
self.save_state()
self.restart()
def on_cs_devices(self, idx, device):
state, cs = self.get_state(), self.get_control_sheet()
marker_type = state.marker_type
if device is not None and \
( (marker_type == MarkerType.OPENCV_LBF and state.opencv_lbf_state.device == device) or \
(marker_type == MarkerType.GOOGLE_FACEMESH and state.google_facemesh_state.device == device) ):
marker_state = state.get_marker_state()
if state.marker_type == MarkerType.OPENCV_LBF:
self.opencv_lbf = lib_cv.FaceMarkerLBF()
elif state.marker_type == MarkerType.GOOGLE_FACEMESH:
self.google_facemesh = onnx_models.FaceMesh(state.google_facemesh_state.device)
cs.marker_coverage.enable()
cs.marker_coverage.set_config(lib_csw.Number.Config(min=0.1, max=3.0, step=0.1, decimals=1, allow_instant_update=True))
marker_coverage = marker_state.marker_coverage
if marker_coverage is None:
if marker_type == MarkerType.OPENCV_LBF:
marker_coverage = 1.1
elif marker_type == MarkerType.GOOGLE_FACEMESH:
marker_coverage = 1.3
cs.marker_coverage.set_number(marker_coverage)
cs.temporal_smoothing.enable()
cs.temporal_smoothing.set_config(lib_csw.Number.Config(min=1, max=10, step=1, allow_instant_update=True))
cs.temporal_smoothing.set_number(marker_state.temporal_smoothing if marker_state.temporal_smoothing is not None else 1)
else:
if marker_type == MarkerType.OPENCV_LBF:
state.opencv_lbf_state.device = device
elif marker_type == MarkerType.GOOGLE_FACEMESH:
state.google_facemesh_state.device = device
self.save_state()
self.restart()
def on_cs_marker_coverage(self, marker_coverage):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.marker_coverage.get_config()
marker_coverage = state.get_marker_state().marker_coverage = np.clip(marker_coverage, cfg.min, cfg.max)
cs.marker_coverage.set_number(marker_coverage)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_temporal_smoothing(self, temporal_smoothing):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.temporal_smoothing.get_config()
temporal_smoothing = state.get_marker_state().temporal_smoothing = int(np.clip(temporal_smoothing, cfg.min, cfg.max))
if temporal_smoothing == 1:
self.temporal_lmrks = []
cs.temporal_smoothing.set_number(temporal_smoothing)
self.save_state()
self.reemit_frame_signal.send()
def on_tick(self):
state, cs = self.get_state(), self.get_control_sheet()
if self.pending_bcd is None:
self.start_profile_timing()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
is_frame_reemitted = bcd.get_is_frame_reemitted()
marker_type = state.marker_type
marker_state = state.get_marker_state()
is_opencv_lbf = marker_type == MarkerType.OPENCV_LBF and self.opencv_lbf is not None
is_google_facemesh = marker_type == MarkerType.GOOGLE_FACEMESH and self.google_facemesh is not None
if marker_type is not None:
frame_name = bcd.get_frame_name()
frame_image = bcd.get_image(frame_name)
if all_is_not_None(frame_image) and (is_opencv_lbf or is_google_facemesh):
face_mark_list = bcd.get_face_mark_list()
if marker_state.temporal_smoothing != 1 and \
len(self.temporal_lmrks) != len(face_mark_list):
self.temporal_lmrks = [ [] for _ in range(len(face_mark_list)) ]
for face_id, face_mark in enumerate(face_mark_list):
face_mark_rect = face_mark.get_face_urect()
if face_mark_rect is not None:
# Cut the face to feed to the face marker
face_image, face_uni_mat = face_mark_rect.cut(frame_image, marker_state.marker_coverage, 256 if is_opencv_lbf else \
192 if is_google_facemesh else 0 )
_,H,W,_ = ImageProcessor(face_image).get_dims()
if is_opencv_lbf:
lmrks = self.opencv_lbf.extract(face_image)[0]
elif is_google_facemesh:
lmrks = self.google_facemesh.extract(face_image)[0][...,0:2]
lmrks /= (W,H)
if marker_state.temporal_smoothing != 1:
if not is_frame_reemitted or len(self.temporal_lmrks[face_id]) == 0:
self.temporal_lmrks[face_id].append(lmrks)
self.temporal_lmrks[face_id] = self.temporal_lmrks[face_id][-marker_state.temporal_smoothing:]
lmrks = np.mean(self.temporal_lmrks[face_id],0 )
face_ulmrks = FaceULandmarks.create (FaceULandmarks.Type.LANDMARKS_2D_68 if is_opencv_lbf else \
FaceULandmarks.Type.LANDMARKS_2D_468 if is_google_facemesh else None, lmrks)
face_ulmrks = face_ulmrks.transform(face_uni_mat, invert=True)
face_mark.add_face_ulandmarks (face_ulmrks)
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
else:
time.sleep(0.001)
class MarkerState(BackendWorkerState):
marker_coverage : float = None
temporal_smoothing : int = None
class OpenCVLBFState(BackendWorkerState):
device = None
class GoogleFaceMeshState(BackendWorkerState):
device = None
class WorkerState(BackendWorkerState):
def __init__(self):
self.marker_type : MarkerType = None
self.marker_state = {}
self.opencv_lbf_state = OpenCVLBFState()
self.google_facemesh_state = GoogleFaceMeshState()
def get_marker_state(self) -> MarkerState:
state = self.marker_state.get(self.marker_type, None)
if state is None:
state = self.marker_state[self.marker_type] = MarkerState()
return state
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.marker_type = lib_csw.DynamicSingleSwitch.Client()
self.device = lib_csw.DynamicSingleSwitch.Client()
self.marker_coverage = lib_csw.Number.Client()
self.temporal_smoothing = lib_csw.Number.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.marker_type = lib_csw.DynamicSingleSwitch.Host()
self.device = lib_csw.DynamicSingleSwitch.Host()
self.marker_coverage = lib_csw.Number.Host()
self.temporal_smoothing = lib_csw.Number.Host()
# lmrks_list = []
# offsets = [ (0,0), (-2,0), (0,-2), (2,0), (0,2) ]
# for x_off, y_off in offsets:
# feed_image = face_image.copy()
# if x_off > 0:
# feed_image[:-x_off] = feed_image[x_off:]
# elif x_off < 0:
# feed_image[x_off:] = feed_image[:-x_off]
# if y_off > 0:
# feed_image[:,:-y_off] = feed_image[:,y_off:]
# elif y_off < 0:
# feed_image[:,y_off:] = feed_image[:,:-y_off]
# if marker_type == MarkerType.OPENCV_LBF:
# lmrks = self.opencv_lbf.extract(feed_image)
# lmrks_list.append(lmrks)
# #print(lmrks_list)
# lmrks = np.mean(lmrks_list, 0)
# self.temporal_lmrks.append(lmrks)
# if len(self.temporal_lmrks) >= 5:
# self.temporal_lmrks.pop(0)
# lmrks = np.mean(self.temporal_lmrks,0 )
# x = 4
# spatial_offsets = [ (0,0), (-x,0), (0,-x), (x,0), (0,x) ]
# lmrks_list = []
# for i in range(temporal_smoothing):
# if temporal_smoothing == 1:
# feed_image = face_image
# else:
# feed_image = face_image.copy()
# x_off,y_off = spatial_offsets[i]
# if x_off > 0:
# feed_image[:-x_off] = feed_image[x_off:]
# elif x_off < 0:
# feed_image[x_off:] = feed_image[:-x_off]
# if y_off > 0:
# feed_image[:,:-y_off] = feed_image[:,y_off:]
# elif y_off < 0:
# feed_image[:,y_off:] = feed_image[:,:-y_off]
# lmrks_list.append(lmrks)
# lmrks = np.mean(lmrks_list, 0)

View file

@ -0,0 +1,303 @@
import time
from enum import IntEnum
import cupy as cp
import numexpr as ne
import numpy as np
from xlib import cupy as lib_cp
from xlib import os as lib_os
from xlib.image import ImageProcessor
from xlib.mp import csw as lib_csw
from xlib.python import all_is_not_None
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class FaceMaskType(IntEnum):
SRC = 0
CELEB = 1
SRC_M_CELEB = 2
FaceMaskTypeNames = ['@FaceMerger.FaceMaskType.SRC','@FaceMerger.FaceMaskType.CELEB','@FaceMerger.FaceMaskType.SRC_M_CELEB']
class FaceMerger(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FaceMergerWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out] )
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class FaceMergerWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.bc_out = bc_out
self.pending_bcd = None
self.is_gpu = False
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.device.call_on_selected(self.on_cs_device)
cs.face_x_offset.call_on_number(self.on_cs_face_x_offset)
cs.face_y_offset.call_on_number(self.on_cs_face_y_offset)
cs.face_scale.call_on_number(self.on_cs_face_scale)
cs.face_mask_erode.call_on_number(self.on_cs_mask_erode)
cs.face_mask_blur.call_on_number(self.on_cs_mask_blur)
cs.face_opacity.call_on_number(self.on_cs_face_opacity)
cs.device.enable()
cs.device.set_choices( ['CPU'] + lib_cp.get_available_devices(), none_choice_name='@misc.menu_select')
cs.device.select(state.device if state.device is not None else 'CPU')
def on_cs_device(self, idxs, device : lib_cp.CuPyDeviceInfo):
state, cs = self.get_state(), self.get_control_sheet()
if device is not None and state.device == device:
cs.face_x_offset.enable()
cs.face_x_offset.set_config(lib_csw.Number.Config(min=-0.5, max=0.5, step=0.001, decimals=3, allow_instant_update=True))
cs.face_x_offset.set_number(state.face_x_offset if state.face_x_offset is not None else 0.0)
cs.face_y_offset.enable()
cs.face_y_offset.set_config(lib_csw.Number.Config(min=-0.5, max=0.5, step=0.001, decimals=3, allow_instant_update=True))
cs.face_y_offset.set_number(state.face_y_offset if state.face_y_offset is not None else 0.0)
cs.face_scale.enable()
cs.face_scale.set_config(lib_csw.Number.Config(min=0.5, max=1.5, step=0.01, decimals=2, allow_instant_update=True))
cs.face_scale.set_number(state.face_scale if state.face_scale is not None else 1.0)
cs.face_mask_type.call_on_selected(self.on_cs_face_mask_type)
cs.face_mask_type.enable()
cs.face_mask_type.set_choices(FaceMaskType, FaceMaskTypeNames, none_choice_name=None)
cs.face_mask_type.select(state.face_mask_type if state.face_mask_type is not None else FaceMaskType.SRC_M_CELEB)
cs.face_mask_erode.enable()
cs.face_mask_erode.set_config(lib_csw.Number.Config(min=-400, max=400, step=1, decimals=0, allow_instant_update=True))
cs.face_mask_erode.set_number(state.face_mask_erode if state.face_mask_erode is not None else 5.0)
cs.face_mask_blur.enable()
cs.face_mask_blur.set_config(lib_csw.Number.Config(min=0, max=400, step=1, decimals=0, allow_instant_update=True))
cs.face_mask_blur.set_number(state.face_mask_blur if state.face_mask_blur is not None else 25.0)
cs.face_opacity.enable()
cs.face_opacity.set_config(lib_csw.Number.Config(min=0.0, max=1.0, step=0.01, decimals=2, allow_instant_update=True))
cs.face_opacity.set_number(state.face_opacity if state.face_opacity is not None else 1.0)
if device != 'CPU':
self.is_gpu = True
cp.cuda.Device( device.get_index() ).use()
self.cp_mask_clip_kernel = cp.ElementwiseKernel('T x', 'T z', 'z = x < 0.004 ? 0 : x > 1.0 ? 1.0 : x', 'mask_clip_kernel')
self.cp_merge_kernel = cp.ElementwiseKernel('T bg, T face, T mask', 'T z', 'z = bg*(1.0-mask) + face*mask', 'merge_kernel')
self.cp_merge_kernel_opacity = cp.ElementwiseKernel('T bg, T face, T mask, T opacity', 'T z', 'z = bg*(1.0-mask) + bg*mask*(1.0-opacity) + face*mask*opacity', 'merge_kernel_opacity')
else:
state.device = device
self.save_state()
self.restart()
def on_cs_face_x_offset(self, face_x_offset):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.face_x_offset.get_config()
face_x_offset = state.face_x_offset = float(np.clip(face_x_offset, cfg.min, cfg.max))
cs.face_x_offset.set_number(face_x_offset)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_face_y_offset(self, face_y_offset):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.face_y_offset.get_config()
face_y_offset = state.face_y_offset = float(np.clip(face_y_offset, cfg.min, cfg.max))
cs.face_y_offset.set_number(face_y_offset)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_face_scale(self, face_scale):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.face_scale.get_config()
face_scale = state.face_scale = float(np.clip(face_scale, cfg.min, cfg.max))
cs.face_scale.set_number(face_scale)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_face_mask_type(self, idx, face_mask_type):
state, cs = self.get_state(), self.get_control_sheet()
state.face_mask_type = face_mask_type
self.save_state()
self.reemit_frame_signal.send()
def on_cs_mask_erode(self, face_mask_erode):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.face_mask_erode.get_config()
face_mask_erode = state.face_mask_erode = int(np.clip(face_mask_erode, cfg.min, cfg.max))
cs.face_mask_erode.set_number(face_mask_erode)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_mask_blur(self, face_mask_blur):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.face_mask_blur.get_config()
face_mask_blur = state.face_mask_blur = int(np.clip(face_mask_blur, cfg.min, cfg.max))
cs.face_mask_blur.set_number(face_mask_blur)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_face_opacity(self, face_opacity):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.face_opacity.get_config()
face_opacity = state.face_opacity = float(np.clip(face_opacity, cfg.min, cfg.max))
cs.face_opacity.set_number(face_opacity)
self.save_state()
self.reemit_frame_signal.send()
def on_tick(self):
state, cs = self.get_state(), self.get_control_sheet()
if self.pending_bcd is None:
self.start_profile_timing()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
frame_name = bcd.get_frame_name()
frame_image = bcd.get_image(frame_name)
if frame_image is not None:
for face_mark in bcd.get_face_mark_list():
face_align = face_mark.get_face_align()
if face_align is not None:
face_swap = face_align.get_face_swap()
face_align_mask = face_align.get_face_mask()
if face_swap is not None:
face_swap_mask = face_swap.get_face_mask()
if face_swap_mask is not None:
face_align_img = bcd.get_image(face_align.get_image_name())
face_swap_img = bcd.get_image(face_swap.get_image_name())
face_align_mask_img = bcd.get_image(face_align_mask.get_image_name())
face_swap_mask_img = bcd.get_image(face_swap_mask.get_image_name())
source_to_aligned_uni_mat = face_align.get_source_to_aligned_uni_mat()
face_mask_type = state.face_mask_type
if all_is_not_None(face_align_img, face_align_mask_img, face_swap_img, face_swap_mask_img, face_mask_type):
face_height, face_width = face_align_img.shape[:2]
if self.is_gpu:
frame_image = cp.asarray(frame_image)
face_align_mask_img = cp.asarray(face_align_mask_img)
face_swap_mask_img = cp.asarray(face_swap_mask_img)
face_swap_img = cp.asarray(face_swap_img)
frame_image_ip = ImageProcessor(frame_image).to_ufloat32()
frame_image, (_, frame_height, frame_width, _) = frame_image_ip.get_image('HWC'), frame_image_ip.get_dims()
face_align_mask_img = ImageProcessor(face_align_mask_img).to_ufloat32().get_image('HW')
face_swap_mask_img = ImageProcessor(face_swap_mask_img).to_ufloat32().get_image('HW')
aligned_to_source_uni_mat = source_to_aligned_uni_mat.invert()
aligned_to_source_uni_mat = aligned_to_source_uni_mat.source_translated(-state.face_x_offset, -state.face_y_offset)
aligned_to_source_uni_mat = aligned_to_source_uni_mat.source_scaled_around_center(state.face_scale,state.face_scale)
aligned_to_source_uni_mat = aligned_to_source_uni_mat.to_exact_mat (face_width, face_height, frame_width, frame_height)
if face_mask_type == FaceMaskType.SRC:
face_mask = face_align_mask_img
elif face_mask_type == FaceMaskType.CELEB:
face_mask = face_swap_mask_img
elif face_mask_type == FaceMaskType.SRC_M_CELEB:
face_mask = face_align_mask_img*face_swap_mask_img
# Combine face mask
face_mask_ip = ImageProcessor(face_mask).erode_blur(state.face_mask_erode, state.face_mask_blur, fade_to_border=True) \
.warpAffine(aligned_to_source_uni_mat, frame_width, frame_height)
if self.is_gpu:
face_mask_ip.apply( lambda img: self.cp_mask_clip_kernel(img) )
else:
face_mask_ip.clip2( (1.0/255.0), 0.0, 1.0, 1.0)
frame_face_mask = face_mask_ip.get_image('HWC')
frame_face_swap_img = ImageProcessor(face_swap_img) \
.to_ufloat32().warpAffine(aligned_to_source_uni_mat, frame_width, frame_height).get_image('HWC')
# Combine final frame
opacity = state.face_opacity
if self.is_gpu:
if opacity == 1.0:
frame_final = self.cp_merge_kernel(frame_image, frame_face_swap_img, frame_face_mask)
else:
frame_final = self.cp_merge_kernel_opacity(frame_image, frame_face_swap_img, frame_face_mask, opacity)
frame_final = cp.asnumpy(frame_final)
else:
if opacity == 1.0:
frame_final = ne.evaluate('frame_image*(1.0-frame_face_mask) + frame_face_swap_img*frame_face_mask')
else:
frame_final = ne.evaluate('frame_image*(1.0-frame_face_mask) + frame_image*frame_face_mask*(1.0-opacity) + frame_face_swap_img*frame_face_mask*opacity')
# keep image in float32 in order not to extra load FaceMerger
merged_frame_name = f'{frame_name}_merged'
bcd.set_merged_frame_name(merged_frame_name)
bcd.set_image(merged_frame_name, frame_final)
break
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
else:
time.sleep(0.001)
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.device = lib_csw.DynamicSingleSwitch.Client()
self.face_x_offset = lib_csw.Number.Client()
self.face_y_offset = lib_csw.Number.Client()
self.face_scale = lib_csw.Number.Client()
self.face_mask_type = lib_csw.DynamicSingleSwitch.Client()
self.face_mask_x_offset = lib_csw.Number.Client()
self.face_mask_y_offset = lib_csw.Number.Client()
self.face_mask_scale = lib_csw.Number.Client()
self.face_mask_erode = lib_csw.Number.Client()
self.face_mask_blur = lib_csw.Number.Client()
self.face_opacity = lib_csw.Number.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.device = lib_csw.DynamicSingleSwitch.Host()
self.face_x_offset = lib_csw.Number.Host()
self.face_y_offset = lib_csw.Number.Host()
self.face_scale = lib_csw.Number.Host()
self.face_mask_type = lib_csw.DynamicSingleSwitch.Host()
self.face_mask_erode = lib_csw.Number.Host()
self.face_mask_blur = lib_csw.Number.Host()
self.face_opacity = lib_csw.Number.Host()
class WorkerState(BackendWorkerState):
device : lib_cp.CuPyDeviceInfo = None
face_x_offset : float = None
face_y_offset : float = None
face_scale : float = None
face_mask_type = None
face_mask_erode : int = None
face_mask_blur : int = None
face_opacity : float = None

View file

@ -0,0 +1,339 @@
import time
from pathlib import Path
import numpy as np
from modelhub import DFLive
from xlib import os as lib_os
from xlib.facemeta import FaceMask, FaceSwap
from xlib.image.ImageProcessor import ImageProcessor
from xlib.mp import csw as lib_csw
from xlib.python import all_is_not_None
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class FaceSwapper(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, dfm_models_path : Path, backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FaceSwapperWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out, dfm_models_path])
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class FaceSwapperWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, dfm_models_path : Path):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.bc_out = bc_out
self.dfm_models_path = dfm_models_path
self.pending_bcd = None
self.dfm_model_initializer = None
self.dfm_model = None
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.model.call_on_selected(self.on_cs_model_info)
cs.device.call_on_selected(self.on_cs_device)
cs.face_id.call_on_number(self.on_cs_face_id)
cs.morph_factor.call_on_number(self.on_cs_morph_factor)
cs.presharpen_amount.call_on_number(self.on_cs_presharpen_amount)
cs.pre_gamma_red.call_on_number(self.on_cs_gamma_red)
cs.pre_gamma_green.call_on_number(self.on_cs_pre_gamma_green)
cs.pre_gamma_blue.call_on_number(self.on_cs_pre_gamma_blue)
cs.two_pass.call_on_flag(self.on_cs_two_pass)
cs.model.enable()
cs.model.set_choices( DFLive.get_available_models_info(dfm_models_path), none_choice_name='@misc.menu_select')
cs.model.select(state.model)
def on_cs_model_info(self, idx, model):
state, cs = self.get_state(), self.get_control_sheet()
if state.model == model:
cs.device.enable()
cs.device.set_choices( DFLive.get_available_devices(), none_choice_name='@misc.menu_select')
cs.device.select(state.model_state.device)
else:
state.model = model
state.model_state = ModelState()
self.save_state()
self.restart()
def on_cs_device(self, idxs, device):
state, cs = self.get_state(), self.get_control_sheet()
if device is not None and state.model_state.device == device:
self.dfm_model_initializer = DFLive.DFMModel_from_info(state.model, device)
self.set_busy(True)
else:
state.model_state = ModelState()
state.model_state.device = device
self.save_state()
self.restart()
def on_cs_face_id(self, face_id):
state, cs = self.get_state(), self.get_control_sheet()
model_state = state.model_state
cfg = cs.face_id.get_config()
if model_state is not None:
face_id = model_state.face_id = int(np.clip(face_id, cfg.min, cfg.max))
cs.face_id.set_number(face_id)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_presharpen_amount(self, presharpen_amount):
state, cs = self.get_state(), self.get_control_sheet()
model_state = state.model_state
cfg = cs.presharpen_amount.get_config()
if model_state is not None:
presharpen_amount = model_state.presharpen_amount = float(np.clip(presharpen_amount, cfg.min, cfg.max))
cs.presharpen_amount.set_number(presharpen_amount)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_morph_factor(self, morph_factor):
state, cs = self.get_state(), self.get_control_sheet()
model_state = state.model_state
cfg = cs.morph_factor.get_config()
if model_state is not None:
morph_factor = model_state.morph_factor = float(np.clip(morph_factor, cfg.min, cfg.max))
cs.morph_factor.set_number(morph_factor)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_gamma_red(self, pre_gamma_red):
state, cs = self.get_state(), self.get_control_sheet()
model_state = state.model_state
cfg = cs.pre_gamma_red.get_config()
if model_state is not None:
pre_gamma_red = model_state.pre_gamma_red = float(np.clip(pre_gamma_red, cfg.min, cfg.max))
cs.pre_gamma_red.set_number(pre_gamma_red)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_pre_gamma_green(self, pre_gamma_green):
state, cs = self.get_state(), self.get_control_sheet()
model_state = state.model_state
cfg = cs.pre_gamma_green.get_config()
if model_state is not None:
pre_gamma_green = model_state.pre_gamma_green = float(np.clip(pre_gamma_green, cfg.min, cfg.max))
cs.pre_gamma_green.set_number(pre_gamma_green)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_pre_gamma_blue(self, pre_gamma_blue):
state, cs = self.get_state(), self.get_control_sheet()
model_state = state.model_state
cfg = cs.pre_gamma_blue.get_config()
if model_state is not None:
pre_gamma_blue = model_state.pre_gamma_blue = float(np.clip(pre_gamma_blue, cfg.min, cfg.max))
cs.pre_gamma_blue.set_number(pre_gamma_blue)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_two_pass(self, two_pass):
state, cs = self.get_state(), self.get_control_sheet()
model_state = state.model_state
if model_state is not None:
model_state.two_pass = two_pass
self.save_state()
self.reemit_frame_signal.send()
def on_tick(self):
state, cs = self.get_state(), self.get_control_sheet()
if self.dfm_model_initializer is not None:
events = self.dfm_model_initializer.process_events()
if events.prev_status_downloading:
cs.model_dl_progress.disable()
if events.new_status_downloading:
cs.model_dl_progress.enable()
cs.model_dl_progress.set_config( lib_csw.Progress.Config(title='@FaceSwapper.downloading_model') )
cs.model_dl_progress.set_progress(0)
elif events.new_status_initialized:
self.dfm_model = events.dfm_model
self.dfm_model_initializer = None
model_width, model_height = self.dfm_model.get_input_res()
cs.model_info_label.enable()
cs.model_info_label.set_config( lib_csw.InfoLabel.Config(info_icon=True,
info_lines=[f'@FaceSwapper.model_information',
'',
f'@FaceSwapper.filename',
f'{self.dfm_model.get_model_path().name}',
'',
f'@FaceSwapper.resolution',
f'{model_width}x{model_height}']) )
cs.face_id.enable()
cs.face_id.set_config(lib_csw.Number.Config(min=0, max=16, step=1, decimals=0, allow_instant_update=True))
cs.face_id.set_number(state.model_state.face_id if state.model_state.face_id is not None else 0)
if self.dfm_model.has_morph_value():
cs.morph_factor.enable()
cs.morph_factor.set_config(lib_csw.Number.Config(min=0, max=1, step=0.01, decimals=2, allow_instant_update=True))
cs.morph_factor.set_number(state.model_state.morph_factor if state.model_state.morph_factor is not None else 0.75)
cs.presharpen_amount.enable()
cs.presharpen_amount.set_config(lib_csw.Number.Config(min=0, max=10, step=0.1, decimals=1, allow_instant_update=True))
cs.presharpen_amount.set_number(state.model_state.presharpen_amount if state.model_state.presharpen_amount is not None else 0)
cs.pre_gamma_red.enable()
cs.pre_gamma_red.set_config(lib_csw.Number.Config(min=0.01, max=4, step=0.01, decimals=2, allow_instant_update=True))
cs.pre_gamma_red.set_number(state.model_state.pre_gamma_red if state.model_state.pre_gamma_red is not None else 1)
cs.pre_gamma_green.enable()
cs.pre_gamma_green.set_config(lib_csw.Number.Config(min=0.01, max=4, step=0.01, decimals=2, allow_instant_update=True))
cs.pre_gamma_green.set_number(state.model_state.pre_gamma_green if state.model_state.pre_gamma_green is not None else 1)
cs.pre_gamma_blue.enable()
cs.pre_gamma_blue.set_config(lib_csw.Number.Config(min=0.010, max=4, step=0.01, decimals=2, allow_instant_update=True))
cs.pre_gamma_blue.set_number(state.model_state.pre_gamma_blue if state.model_state.pre_gamma_blue is not None else 1)
cs.two_pass.enable()
cs.two_pass.set_flag(state.model_state.two_pass if state.model_state.two_pass is not None else False)
self.set_busy(False)
self.reemit_frame_signal.send()
elif events.new_status_error:
self.set_busy(False)
cs.model_dl_error.enable()
cs.model_dl_error.set_error(events.error)
if events.download_progress is not None:
cs.model_dl_progress.set_progress(events.download_progress)
if self.pending_bcd is None:
self.start_profile_timing()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
model_state = state.model_state
dfm_model = self.dfm_model
if all_is_not_None(dfm_model, model_state):
face_id = model_state.face_id
if face_id is not None:
for i, face_mark in enumerate(bcd.get_face_mark_list()):
if face_id == i:
face_align = face_mark.get_face_align()
if face_align is not None:
face_align_image_name = face_align.get_image_name()
face_align_image = bcd.get_image(face_align_image_name)
if face_align_image is not None:
pre_gamma_red = model_state.pre_gamma_red
pre_gamma_green = model_state.pre_gamma_green
pre_gamma_blue = model_state.pre_gamma_blue
fai_ip = ImageProcessor(face_align_image)
if model_state.presharpen_amount != 0:
fai_ip.sharpen(factor=model_state.presharpen_amount)
if pre_gamma_red != 1.0 or pre_gamma_green != 1.0 or pre_gamma_blue != 1.0:
fai_ip.adjust_gamma(pre_gamma_red, pre_gamma_green, pre_gamma_blue)
face_align_image = fai_ip.get_image('HWC')
celeb_face, celeb_face_mask_img, face_align_mask_img = dfm_model.convert(face_align_image, morph_factor=model_state.morph_factor)
celeb_face, celeb_face_mask_img, face_align_mask_img = celeb_face[0], celeb_face_mask_img[0], face_align_mask_img[0]
if model_state.two_pass:
celeb_face, celeb_face_mask_img, _ = dfm_model.convert(celeb_face, morph_factor=model_state.morph_factor)
celeb_face, celeb_face_mask_img = celeb_face[0], celeb_face_mask_img[0]
face_align_mask = FaceMask()
face_align_mask.set_image_name(f'{face_align_image_name}_mask')
face_align.set_face_mask(face_align_mask)
bcd.set_image(face_align_mask.get_image_name(), face_align_mask_img)
face_swap = FaceSwap()
face_swap.set_image_name (f"{face_align_image_name}_swapped")
face_align.set_face_swap(face_swap)
bcd.set_image(face_swap.get_image_name(), celeb_face)
face_swap_mask = FaceMask()
face_swap_mask.set_image_name(f'{face_swap.get_image_name()}_mask')
face_swap.set_face_mask(face_swap_mask)
bcd.set_image(face_swap_mask.get_image_name(), celeb_face_mask_img)
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
else:
time.sleep(0.001)
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.model = lib_csw.DynamicSingleSwitch.Client()
self.model_info_label = lib_csw.InfoLabel.Client()
self.model_dl_progress = lib_csw.Progress.Client()
self.model_dl_error = lib_csw.Error.Client()
self.device = lib_csw.DynamicSingleSwitch.Client()
self.face_id = lib_csw.Number.Client()
self.morph_factor = lib_csw.Number.Client()
self.presharpen_amount = lib_csw.Number.Client()
self.pre_gamma_red = lib_csw.Number.Client()
self.pre_gamma_blue = lib_csw.Number.Client()
self.pre_gamma_green = lib_csw.Number.Client()
self.two_pass = lib_csw.Flag.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.model = lib_csw.DynamicSingleSwitch.Host()
self.model_info_label = lib_csw.InfoLabel.Host()
self.model_dl_progress = lib_csw.Progress.Host()
self.model_dl_error = lib_csw.Error.Host()
self.device = lib_csw.DynamicSingleSwitch.Host()
self.face_id = lib_csw.Number.Host()
self.morph_factor = lib_csw.Number.Host()
self.presharpen_amount = lib_csw.Number.Host()
self.pre_gamma_red = lib_csw.Number.Host()
self.pre_gamma_blue = lib_csw.Number.Host()
self.pre_gamma_green = lib_csw.Number.Host()
self.two_pass = lib_csw.Flag.Host()
class ModelState(BackendWorkerState):
device = None
face_id : int = None
morph_factor : float = None
presharpen_amount : float = None
pre_gamma_red : float = None
pre_gamma_blue : float = None
pre_gamma_green: float = None
two_pass : bool = None
class WorkerState(BackendWorkerState):
model : DFLive.DFMModelInfo = None
model_state : ModelState = None

View file

@ -0,0 +1,367 @@
import time
from enum import IntEnum
from pathlib import Path
from xlib import os as lib_os
from xlib import player as lib_player
from xlib.image import ImageProcessor
from xlib.mp import csw as lib_csw
from .BackendBase import (BackendConnection, BackendConnectionData, BackendDB,
BackendHost, BackendSignal, BackendWeakHeap,
BackendWorker, BackendWorkerState)
class InputType(IntEnum):
IMAGE_SEQUENCE = 0
VIDEO_FILE = 1
InputTypeNames = ['@FileSource.image_folder', '@FileSource.video_file']
class FileSource(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap,
reemit_frame_signal : BackendSignal,
bc_out : BackendConnection, backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FileSourceWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_out])
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class FileSourceWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_out : BackendConnection):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_out = bc_out
self.bcd_uid = 0
self.pending_bcd = None
self.fp : lib_player.FramePlayer = None
self.last_p_frame = None
lib_os.set_timer_resolution(4)
state, cs = self.get_state(), self.get_control_sheet()
cs.input_type.call_on_selected(self.on_cs_input_type_selected)
cs.input_paths.call_on_paths(self.on_cs_input_paths)
cs.target_width.call_on_number(self.on_cs_target_width)
cs.fps.call_on_number(self.on_cs_fps)
cs.is_realtime.call_on_flag(self.on_cs_is_realtime)
cs.is_autorewind.call_on_flag(self.on_cs_is_autorewind)
cs.frame_index.call_on_number(self.on_cs_frame_index)
cs.play.call_on_signal(self.on_cs_play)
cs.pause.call_on_signal(self.on_cs_pause)
cs.seek_begin.call_on_signal(self.on_cs_seek_begin)
cs.seek_backward.call_on_signal(self.on_cs_seek_backward)
cs.seek_forward.call_on_signal(self.on_cs_seek_forward)
cs.seek_end.call_on_signal(self.on_cs_seek_end)
cs.input_type.enable()
cs.input_type.set_choices(InputType, InputTypeNames)
cs.input_type.select(state.input_type)
def set_fp(self, fp):
if self.fp != fp:
if self.fp is not None:
self.fp.dispose()
self.fp = fp
#######################
### messages
def on_cs_input_type_selected(self, idx, input_type):
cs, state = self.get_control_sheet(), self.get_state()
cs.input_paths.disable()
cs.target_width.disable()
cs.fps.disable()
cs.is_autorewind.disable()
cs.is_realtime.disable()
cs.frame_index.disable()
cs.frame_count.disable()
cs.play.disable()
cs.pause.disable()
cs.seek_backward.disable()
cs.seek_forward.disable()
cs.seek_begin.disable()
cs.seek_end.disable()
self.set_fp(None)
if input_type is not None:
cs.input_paths.enable()
if input_type == InputType.IMAGE_SEQUENCE:
cs.input_paths.set_config( lib_csw.Paths.Config.Directory() )
elif input_type == InputType.VIDEO_FILE:
cs.input_paths.set_config( lib_csw.Paths.Config.ExistingFile(caption='Video file', suffixes=lib_player.VideoFilePlayer.SUPPORTED_VIDEO_FILE_SUFFIXES ) )
if input_type == state.input_type:
cs.input_paths.set_paths(state.input_path)
else:
state.input_type = input_type
state.input_path = None
cs.input_paths.set_paths(None)
state.fp_state = None
self.save_state()
def on_cs_input_paths(self, paths, prev_paths):
state, cs = self.get_state(), self.get_control_sheet()
cs.input_paths_error.set_error(None)
input_type = state.input_type
input_path = paths[0] if len(paths) != 0 else None
if input_path is not None:
if input_type == InputType.IMAGE_SEQUENCE or \
input_type == InputType.VIDEO_FILE:
cls_ = lib_player.ImageSequencePlayer if input_type == InputType.IMAGE_SEQUENCE \
else lib_player.VideoFilePlayer
err = None
try:
fp = cls_(input_path)
except Exception as e:
err = str(e)
if err is None:
self.set_fp(fp)
fp.req_frame_seek(0, 0)
if input_path == state.input_path and state.fp_state is not None:
target_width = fp.set_target_width(state.fp_state.target_width)
fps = fp.set_fps(state.fp_state.fps)
is_realtime = fp.set_is_realtime(state.fp_state.is_realtime)
is_autorewind = fp.set_is_autorewind(state.fp_state.is_autorewind)
else:
state.input_path = input_path
state.fp_state = FPState()
target_width = state.fp_state.target_width = fp.get_target_width()
fps = state.fp_state.fps = fp.get_fps()
is_realtime = state.fp_state.is_realtime = fp.get_is_realtime()
is_autorewind = state.fp_state.is_autorewind = fp.get_is_autorewind()
cs.target_width.enable()
cs.target_width.set_config(lib_csw.Number.Config(min=0, max=4096, step=4, decimals=0, zero_is_auto=True, allow_instant_update=True))
cs.target_width.set_number(target_width)
cs.fps.enable()
cs.fps.set_config(lib_csw.Number.Config(min=0, max=240, step=1.0, decimals=2, zero_is_auto=True, allow_instant_update=True))
cs.fps.set_number(fps)
cs.is_realtime.enable()
cs.is_realtime.set_flag(is_realtime)
cs.is_autorewind.enable()
cs.is_autorewind.set_flag(is_autorewind)
cs.frame_count.enable()
cs.frame_count.set_number( fp.get_frame_count() )
cs.frame_index.enable()
cs.frame_index.set_number( fp.get_frame_idx() )
cs.play.enable()
cs.pause.freeze()
cs.seek_backward.enable()
cs.seek_forward.enable()
cs.seek_begin.enable()
cs.seek_end.enable()
else:
cs.input_paths_error.set_error(err)
cs.input_paths.set_paths(prev_paths, block_event=True)
self.save_state()
def on_cs_target_width(self, target_width):
state, cs = self.get_state(), self.get_control_sheet()
target_width = state.fp_state.target_width = self.fp.set_target_width(target_width)
cs.target_width.set_number(target_width)
self.save_state()
def on_cs_fps(self, fps):
state, cs = self.get_state(), self.get_control_sheet()
fps = state.fp_state.fps = self.fp.set_fps(fps)
cs.fps.set_number(fps)
self.save_state()
def on_cs_is_realtime(self, is_realtime):
state, cs = self.get_state(), self.get_control_sheet()
is_realtime = state.fp_state.is_realtime = self.fp.set_is_realtime(is_realtime)
cs.is_realtime.set_flag(is_realtime)
if not cs.is_realtime.get_flag():
cs.fps.freeze()
else:
cs.fps.enable()
self.save_state()
def on_cs_is_autorewind(self, is_autorewind):
state, cs = self.get_state(), self.get_control_sheet()
is_autorewind = state.fp_state.is_autorewind = self.fp.set_is_autorewind(is_autorewind)
cs.is_autorewind.set_flag(is_autorewind)
self.save_state()
def on_cs_play(self):
self.fp.req_play_start()
def on_cs_pause(self):
self.fp.req_play_stop()
def on_cs_seek_begin(self):
self.fp.req_frame_seek(0,0)
def on_cs_seek_backward(self):
self.fp.req_frame_seek(-1,1)
def on_cs_seek_forward(self):
self.fp.req_frame_seek(1,1)
def on_cs_seek_end(self):
self.fp.req_frame_seek(0,2)
def on_cs_frame_index(self, frame_idx):
self.fp.req_frame_seek(frame_idx, 0)
def on_fp_state_change(self, fp, is_playing):
state, cs = self.get_state(), self.get_control_sheet()
if is_playing:
cs.play.freeze()
cs.pause.enable()
cs.seek_backward.freeze()
cs.seek_forward.freeze()
else:
cs.play.enable()
cs.pause.freeze()
cs.seek_backward.enable()
cs.seek_forward.enable()
def on_tick(self):
if self.fp is not None:
state, cs = self.get_state(), self.get_control_sheet()
if state.fp_state.is_realtime or self.pending_bcd is None:
self.start_profile_timing()
reemit_frame = self.reemit_frame_signal.recv()
pr = self.fp.process()
if pr.new_is_playing is not None:
if pr.new_is_playing:
cs.play.freeze()
cs.pause.enable()
cs.seek_backward.freeze()
cs.seek_forward.freeze()
else:
cs.play.enable()
cs.pause.freeze()
cs.seek_backward.enable()
cs.seek_forward.enable()
if pr.new_frame_idx is not None:
cs.frame_index.set_number(pr.new_frame_idx, block_event=True)
p_frame = pr.new_frame
if p_frame is not None:
self.bcd_uid += 1
elif reemit_frame and self.last_p_frame is not None:
p_frame = self.last_p_frame
if p_frame is not None:
# Frame is changed or reemit, construct new ds
self.last_p_frame = p_frame
bcd = BackendConnectionData(uid=self.bcd_uid)
bcd.assign_weak_heap(self.weak_heap)
bcd.set_is_frame_reemitted(reemit_frame)
bcd.set_frame_count(p_frame.frame_count)
bcd.set_frame_num(p_frame.frame_num)
bcd.set_frame_fps(p_frame.fps)
bcd.set_frame_timestamp(p_frame.timestamp)
bcd.set_frame_name(p_frame.name)
image = ImageProcessor(p_frame.image).to_uint8().get_image('HWC')
bcd.set_image(p_frame.name, image)
if p_frame.error is not None:
bcd.add_error(p_frame.error)
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
time.sleep(0.001)
def on_stop(self):
self.set_fp(None)
class FPState(BackendWorkerState):
target_width : int = None
fps : float = None
is_realtime : bool = None
is_autorewind : bool = None
class WorkerState(BackendWorkerState):
input_type : InputType = None
input_path : Path = None
fp_state : FPState = None
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.input_type = lib_csw.DynamicSingleSwitch.Client()
self.input_paths = lib_csw.Paths.Client()
self.input_paths_error = lib_csw.Error.Client()
self.target_width = lib_csw.Number.Client()
self.fps = lib_csw.Number.Client()
self.is_realtime = lib_csw.Flag.Client()
self.is_autorewind = lib_csw.Flag.Client()
self.frame_index = lib_csw.Number.Client()
self.frame_count = lib_csw.Number.Client()
self.play = lib_csw.Signal.Client()
self.pause = lib_csw.Signal.Client()
self.seek_backward = lib_csw.Signal.Client()
self.seek_forward = lib_csw.Signal.Client()
self.seek_begin = lib_csw.Signal.Client()
self.seek_end = lib_csw.Signal.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.input_type = lib_csw.DynamicSingleSwitch.Host()
self.input_paths = lib_csw.Paths.Host()
self.input_paths_error = lib_csw.Error.Host()
self.target_width = lib_csw.Number.Host()
self.fps = lib_csw.Number.Host()
self.is_realtime = lib_csw.Flag.Host()
self.is_autorewind = lib_csw.Flag.Host()
self.frame_index = lib_csw.Number.Host()
self.frame_count = lib_csw.Number.Host()
self.play = lib_csw.Signal.Host()
self.pause = lib_csw.Signal.Host()
self.seek_backward = lib_csw.Signal.Host()
self.seek_forward = lib_csw.Signal.Host()
self.seek_begin = lib_csw.Signal.Host()
self.seek_end = lib_csw.Signal.Host()

View file

@ -0,0 +1,111 @@
import time
import numpy as np
from xlib import os as lib_os
from xlib.image import ImageProcessor
from xlib.mp import csw as lib_csw
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class FrameAdjuster(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FrameAdjusterWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out] )
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class FrameAdjusterWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.bc_out = bc_out
self.pending_bcd = None
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.median_blur_per.call_on_number(self.on_cs_median_blur_per)
cs.degrade_bicubic_per.call_on_number(self.on_cs_degrade_bicubic_per)
cs.median_blur_per.enable()
cs.median_blur_per.set_config(lib_csw.Number.Config(min=0, max=100, step=1, decimals=0, allow_instant_update=True))
cs.median_blur_per.set_number(state.median_blur_per if state.median_blur_per is not None else 0)
cs.degrade_bicubic_per.enable()
cs.degrade_bicubic_per.set_config(lib_csw.Number.Config(min=0, max=100, step=1, decimals=0, allow_instant_update=True))
cs.degrade_bicubic_per.set_number(state.degrade_bicubic_per if state.degrade_bicubic_per is not None else 0)
def on_cs_median_blur_per(self, median_blur_per):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.median_blur_per.get_config()
median_blur_per = state.median_blur_per = int(np.clip(median_blur_per, cfg.min, cfg.max))
cs.median_blur_per.set_number(median_blur_per)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_degrade_bicubic_per(self, degrade_bicubic_per):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.degrade_bicubic_per.get_config()
degrade_bicubic_per = state.degrade_bicubic_per = int(np.clip(degrade_bicubic_per, cfg.min, cfg.max))
cs.degrade_bicubic_per.set_number(degrade_bicubic_per)
self.save_state()
self.reemit_frame_signal.send()
def on_tick(self):
state, cs = self.get_state(), self.get_control_sheet()
if self.pending_bcd is None:
self.start_profile_timing()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
frame_name = bcd.get_frame_name()
frame_image = bcd.get_image(frame_name)
if frame_image is not None:
frame_image_ip = ImageProcessor(frame_image)
frame_image_ip.median_blur(5, state.median_blur_per / 100.0 )
frame_image_ip.degrade_resize( state.degrade_bicubic_per / 100.0, interpolation=ImageProcessor.Interpolation.CUBIC)
frame_image = frame_image_ip.get_image('HWC')
bcd.set_image(frame_name, frame_image)
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
else:
time.sleep(0.001)
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.median_blur_per = lib_csw.Number.Client()
self.degrade_bicubic_per = lib_csw.Number.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.median_blur_per = lib_csw.Number.Host()
self.degrade_bicubic_per = lib_csw.Number.Host()
class WorkerState(BackendWorkerState):
median_blur_per : int = None
degrade_bicubic_per : int = None

View file

@ -0,0 +1,276 @@
from enum import IntEnum
from pathlib import Path
from typing import List
import cv2
import numpy as np
from xlib import cv as lib_cv
from xlib import logic as lib_logic
from xlib import os as lib_os
from xlib import time as lib_time
from xlib.image import ImageProcessor
from xlib.mp import csw as lib_csw
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class StreamOutput(BackendHost):
"""
Bufferizes and shows the stream in separated window.
"""
def __init__(self, weak_heap : BackendWeakHeap,
reemit_frame_signal : BackendSignal,
bc_in : BackendConnection,
save_default_path : Path = None,
backend_db : BackendDB = None):
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=StreamOutputWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, save_default_path] )
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
class SourceType(IntEnum):
ALIGNED_FACE = 0
SWAPPED_FACE = 1
MERGED_FRAME = 2
ViewModeNames = ['@StreamOutput.SourceType.ALIGNED_FACE', '@StreamOutput.SourceType.SWAPPED_FACE', '@StreamOutput.SourceType.MERGED_FRAME']
class StreamOutputWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal,
bc_in : BackendConnection,
save_default_path : Path):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.fps_counter = lib_time.FPSCounter()
self.buffered_frames = lib_logic.DelayedBuffers()
self.is_show_window = False
self.prev_frame_num = -1
self._wnd_name = 'DeepFaceLive output'
self._wnd_showing = False
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.source_type.call_on_selected(self.on_cs_mode)
cs.show_hide_window.call_on_signal(self.on_cs_show_hide_window_signal)
cs.aligned_face_id.call_on_number(self.on_cs_aligned_face_id)
cs.target_delay.call_on_number(self.on_cs_target_delay)
cs.save_sequence_path.call_on_paths(self.on_cs_save_sequence_path)
cs.save_fill_frame_gap.call_on_flag(self.on_cs_save_fill_frame_gap)
cs.source_type.enable()
cs.source_type.set_choices(SourceType, ViewModeNames, none_choice_name='@misc.menu_select')
cs.source_type.select(state.source_type)
cs.target_delay.enable()
cs.target_delay.set_config(lib_csw.Number.Config(min=0, max=5000, step=100, decimals=0, allow_instant_update=True))
cs.target_delay.set_number(state.target_delay if state.target_delay is not None else 500)
cs.avg_fps.enable()
cs.avg_fps.set_config(lib_csw.Number.Config(min=0, max=240, decimals=1, read_only=True))
cs.avg_fps.set_number(0)
cs.show_hide_window.enable()
self.hide_window()
if state.is_showing_window is None:
state.is_showing_window = False
if state.is_showing_window:
state.is_showing_window = not state.is_showing_window
cs.show_hide_window.signal()
cs.save_sequence_path.enable()
cs.save_sequence_path.set_config( lib_csw.Paths.Config.Directory('Choose output sequence directory', directory_path=save_default_path) )
cs.save_sequence_path.set_paths(state.sequence_path)
cs.save_fill_frame_gap.enable()
cs.save_fill_frame_gap.set_flag(state.save_fill_frame_gap if state.save_fill_frame_gap is not None else True )
def on_cs_mode(self, idx, source_type):
state, cs = self.get_state(), self.get_control_sheet()
if source_type == SourceType.ALIGNED_FACE:
cs.aligned_face_id.enable()
cs.aligned_face_id.set_config(lib_csw.Number.Config(min=0, max=16, step=1, allow_instant_update=True))
cs.aligned_face_id.set_number(state.aligned_face_id or 0)
else:
cs.aligned_face_id.disable()
state.source_type = source_type
self.save_state()
self.reemit_frame_signal.send()
def show_window(self):
state, cs = self.get_state(), self.get_control_sheet()
cv2.namedWindow(self._wnd_name)
self._wnd_showing = True
def hide_window(self):
state, cs = self.get_state(), self.get_control_sheet()
if self._wnd_showing:
cv2.destroyWindow(self._wnd_name)
self._wnd_showing = False
def on_cs_show_hide_window_signal(self,):
state, cs = self.get_state(), self.get_control_sheet()
state.is_showing_window = not state.is_showing_window
if state.is_showing_window:
cv2.namedWindow(self._wnd_name)
else:
cv2.destroyWindow(self._wnd_name)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_aligned_face_id(self, aligned_face_id):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.aligned_face_id.get_config()
aligned_face_id = state.aligned_face_id = np.clip(aligned_face_id, cfg.min, cfg.max)
cs.aligned_face_id.set_number(aligned_face_id)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_target_delay(self, target_delay):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.target_delay.get_config()
target_delay = state.target_delay = int(np.clip(target_delay, cfg.min, cfg.max))
self.buffered_frames.set_target_delay(target_delay / 1000.0)
cs.target_delay.set_number(target_delay)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_save_sequence_path(self, paths : List[Path], prev_paths):
state, cs = self.get_state(), self.get_control_sheet()
cs.save_sequence_path_error.set_error(None)
sequence_path = paths[0] if len(paths) != 0 else None
if sequence_path is None or sequence_path.exists():
state.sequence_path = sequence_path
cs.save_sequence_path.set_paths(sequence_path, block_event=True)
else:
cs.save_sequence_path_error.set_error(f'{sequence_path} does not exist.')
cs.save_sequence_path.set_paths(prev_paths, block_event=True)
self.save_state()
self.reemit_frame_signal.send()
def on_cs_save_fill_frame_gap(self, save_fill_frame_gap):
state, cs = self.get_state(), self.get_control_sheet()
state.save_fill_frame_gap = save_fill_frame_gap
self.save_state()
def on_tick(self):
cs, state = self.get_control_sheet(), self.get_state()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
cs.avg_fps.set_number( self.fps_counter.step() )
prev_frame_num = self.prev_frame_num
frame_num = self.prev_frame_num = bcd.get_frame_num()
if frame_num < prev_frame_num:
prev_frame_num = self.prev_frame_num = -1
source_type = state.source_type
if source_type is not None and \
(state.is_showing_window or state.sequence_path is not None):
buffered_frames = self.buffered_frames
view_image = None
if source_type == SourceType.MERGED_FRAME:
view_image = bcd.get_image(bcd.get_merged_frame_name())
elif source_type == SourceType.ALIGNED_FACE:
aligned_face_id = state.aligned_face_id
for i, face_mark in enumerate(bcd.get_face_mark_list()):
if aligned_face_id == i:
face_align = face_mark.get_face_align()
if face_align is not None:
view_image = bcd.get_image(face_align.get_image_name())
break
elif source_type == SourceType.SWAPPED_FACE:
for face_mark in bcd.get_face_mark_list():
face_align = face_mark.get_face_align()
if face_align is not None:
face_swap = face_align.get_face_swap()
if face_swap is not None:
view_image = bcd.get_image(face_swap.get_image_name())
break
if view_image is not None:
buffered_frames.add_buffer( bcd.get_frame_timestamp(), view_image )
if state.sequence_path is not None:
img = ImageProcessor(view_image, copy=True).to_uint8().get_image('HWC')
file_ext, cv_args = '.jpg', [int(cv2.IMWRITE_JPEG_QUALITY), 100]
frame_diff = abs(frame_num - prev_frame_num) if state.save_fill_frame_gap else 1
for i in range(frame_diff):
n = frame_num - i
filename = f'{n:06}'
lib_cv.imwrite(state.sequence_path / (filename+file_ext), img, cv_args)
pr = buffered_frames.process()
img = pr.new_data
if state.is_showing_window and img is not None:
cv2.imshow(self._wnd_name, img)
if state.is_showing_window:
cv2.waitKey(1)
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.source_type = lib_csw.DynamicSingleSwitch.Client()
self.aligned_face_id = lib_csw.Number.Client()
self.target_delay = lib_csw.Number.Client()
self.avg_fps = lib_csw.Number.Client()
self.show_hide_window = lib_csw.Signal.Client()
self.save_sequence_path = lib_csw.Paths.Client()
self.save_sequence_path_error = lib_csw.Error.Client()
self.save_fill_frame_gap = lib_csw.Flag.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.source_type = lib_csw.DynamicSingleSwitch.Host()
self.aligned_face_id = lib_csw.Number.Host()
self.target_delay = lib_csw.Number.Host()
self.avg_fps = lib_csw.Number.Host()
self.show_hide_window = lib_csw.Signal.Host()
self.save_sequence_path = lib_csw.Paths.Host()
self.save_sequence_path_error = lib_csw.Error.Host()
self.save_fill_frame_gap = lib_csw.Flag.Host()
class WorkerState(BackendWorkerState):
source_type : SourceType = None
is_showing_window : bool = None
aligned_face_id : int = None
target_delay : int = None
sequence_path : Path = None
save_fill_frame_gap : bool = None

View file

@ -0,0 +1,11 @@
from .BackendBase import (BackendConnection, BackendConnectionData, BackendDB,
BackendSignal, BackendWeakHeap, BackendHost, BackendWorker)
from .CameraSource import CameraSource
from .FaceAligner import FaceAligner
from .FaceDetector import FaceDetector
from .FaceMarker import FaceMarker
from .FaceMerger import FaceMerger
from .FaceSwapper import FaceSwapper
from .FileSource import FileSource
from .FrameAdjuster import FrameAdjuster
from .StreamOutput import StreamOutput

View file

@ -0,0 +1,79 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from ..backend import CameraSource
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
QComboBoxCSWDynamicSingleSwitch
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
class QCameraSource(QBackendPanel):
def __init__(self, backend : CameraSource):
cs = backend.get_control_sheet()
q_device_idx_label = QLabelPopupInfo(label=L('@QCameraSource.device_index') )
q_device_idx = QComboBoxCSWDynamicSingleSwitch(cs.device_idx, reflect_state_widgets=[q_device_idx_label])
q_driver_label = QLabelPopupInfo(label=L('@QCameraSource.driver'), popup_info_text=L('@QCameraSource.help.driver') )
q_driver = QComboBoxCSWDynamicSingleSwitch(cs.driver, reflect_state_widgets=[q_driver_label])
q_resolution_label = QLabelPopupInfo(label=L('@QCameraSource.resolution'), popup_info_text=L('@QCameraSource.help.resolution') )
q_resolution = QComboBoxCSWDynamicSingleSwitch(cs.resolution, reflect_state_widgets=[q_resolution_label])
q_fps_label = QLabelPopupInfo(label=L('@QCameraSource.fps'), popup_info_text=L('@QCameraSource.help.fps') )
q_fps = QSpinBoxCSWNumber(cs.fps, reflect_state_widgets=[q_fps_label])
q_rotation_label = QLabelPopupInfo(label=L('@QCameraSource.rotation') )
q_rotation = QComboBoxCSWDynamicSingleSwitch(cs.rotation, reflect_state_widgets=[q_rotation_label])
q_flip_horizontal_label = QLabelPopupInfo(label=L('@QCameraSource.flip_horizontal') )
q_flip_horizontal = QCheckBoxCSWFlag(cs.flip_horizontal, reflect_state_widgets=[q_flip_horizontal_label])
q_camera_settings_group_label = QLabelPopupInfo(label=L('@QCameraSource.camera_settings') )
q_open_settings = QXPushButtonCSWSignal(cs.open_settings, text=L('@QCameraSource.open_settings'))
q_load_settings = QXPushButtonCSWSignal(cs.load_settings, text=L('@QCameraSource.load_settings'), reflect_state_widgets=[q_camera_settings_group_label])
q_save_settings = QXPushButtonCSWSignal(cs.save_settings, text=L('@QCameraSource.save_settings'))
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_device_idx_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_device_idx, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_driver_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_driver, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_resolution_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_resolution, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
btn_height = 24
grid_l.addWidget(q_camera_settings_group_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget( lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([q_open_settings]), ),
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([q_load_settings]), ),
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([q_save_settings]), ),
], contents_margins=(1,0,1,0), spacing=1), fixed_height=btn_height), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_fps_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_fps, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_rotation_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_rotation, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_flip_horizontal_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_flip_horizontal, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
super().__init__(backend, L('@QCameraSource.module_title'),
layout=lib_qt.QXVBoxLayout([grid_l], spacing=5),
content_align_top=True)

View file

@ -0,0 +1,40 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from ..backend import FaceAligner
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
class QFaceAligner(QBackendPanel):
def __init__(self, backend : FaceAligner):
cs = backend.get_control_sheet()
q_face_coverage_label = QLabelPopupInfo(label=L('@QFaceAligner.face_coverage'), popup_info_text=L('@QFaceAligner.help.face_coverage') )
q_face_coverage = QSpinBoxCSWNumber(cs.face_coverage, reflect_state_widgets=[q_face_coverage_label])
q_resolution_label = QLabelPopupInfo(label=L('@QFaceAligner.resolution'), popup_info_text=L('@QFaceAligner.help.resolution') )
q_resolution = QSpinBoxCSWNumber(cs.resolution, reflect_state_widgets=[q_resolution_label])
q_exclude_moving_parts_label = QLabelPopupInfo(label=L('@QFaceAligner.exclude_moving_parts'), popup_info_text=L('@QFaceAligner.help.exclude_moving_parts') )
q_exclude_moving_parts = QCheckBoxCSWFlag(cs.exclude_moving_parts, reflect_state_widgets=[q_exclude_moving_parts_label])
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_face_coverage_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_face_coverage, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_resolution_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_resolution, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_exclude_moving_parts_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_exclude_moving_parts, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
super().__init__(backend, L('@QFaceAligner.module_title'),
layout=lib_qt.QXVBoxLayout([grid_l]))

View file

@ -0,0 +1,114 @@
import numpy as np
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from ..backend import FaceDetector
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
QComboBoxCSWDynamicSingleSwitch
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
class QFaceDetector(QBackendPanel):
def __init__(self, backend : FaceDetector):
if not isinstance(backend, FaceDetector):
raise ValueError('backend must be an instance of FaceDetector')
self._backend = backend
self._bc_out = backend.get_bc_out()
self._weak_heap = backend.get_weak_heap()
self._bcd_id = None
self._timer = lib_qt.QXTimer(interval=10, timeout=self._on_timer_10ms, start=True)
face_coords_label = self._q_face_coords_label = lib_qt.QXLabel(font=QXFontDB.get_fixedwidth_font(size=7), word_wrap=False)
q_detected_faces = self._q_detected_faces = lib_qt.QXCollapsibleSection(title=L('@QFaceDetector.detected_faces'),
content_layout=lib_qt.QXVBoxLayout([face_coords_label]), is_opened=True)
cs = backend.get_control_sheet()
q_detector_type_label = QLabelPopupInfo(label=L('@QFaceDetector.detector_type'), popup_info_text=L('@QFaceDetector.help.detector_type') )
q_detector_type = QComboBoxCSWDynamicSingleSwitch(cs.detector_type, reflect_state_widgets=[q_detector_type_label])
q_device_label = QLabelPopupInfo(label=L('@QFaceDetector.device'), popup_info_text=L('@QFaceDetector.help.device') )
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
q_fixed_window_size_label = QLabelPopupInfo(label=L('@QFaceDetector.window_size'), popup_info_text=L('@QFaceDetector.help.window_size') )
q_fixed_window_size = QSpinBoxCSWNumber(cs.fixed_window_size, reflect_state_widgets=[q_fixed_window_size_label])
q_threshold_label = QLabelPopupInfo(label=L('@QFaceDetector.threshold'), popup_info_text=L('@QFaceDetector.help.threshold') )
q_threshold = QSpinBoxCSWNumber(cs.threshold, reflect_state_widgets=[q_threshold_label])
q_max_faces_label = QLabelPopupInfo(label=L('@QFaceDetector.max_faces'), popup_info_text=L('@QFaceDetector.help.max_faces') )
q_max_faces = QSpinBoxCSWNumber(cs.max_faces, reflect_state_widgets=[q_max_faces_label])
q_sort_by_label = QLabelPopupInfo(label=L('@QFaceDetector.sort_by'), popup_info_text=L('@QFaceDetector.help.sort_by') )
q_sort_by = QComboBoxCSWDynamicSingleSwitch(cs.sort_by, reflect_state_widgets=[q_sort_by_label])
q_temporal_smoothing_label = QLabelPopupInfo(label=L('@QFaceDetector.temporal_smoothing'), popup_info_text=L('@QFaceDetector.help.temporal_smoothing') )
q_temporal_smoothing = QSpinBoxCSWNumber(cs.temporal_smoothing, reflect_state_widgets=[q_temporal_smoothing_label])
grid_l = lib_qt.QXGridLayout(vertical_spacing=5, horizontal_spacing=5, )
row = 0
grid_l.addWidget(q_detector_type_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_detector_type, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_device_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_device, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_fixed_window_size_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_fixed_window_size, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_threshold_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_threshold, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addLayout( lib_qt.QXHBoxLayout([q_max_faces_label, 5, q_max_faces]), row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addLayout( lib_qt.QXHBoxLayout([q_sort_by_label, 5,q_sort_by]), row, 2, 1,2,alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
row += 1
grid_l.addLayout( lib_qt.QXHBoxLayout([q_temporal_smoothing_label, 5, q_temporal_smoothing]), row, 0, 1, 4, alignment=Qt.AlignmentFlag.AlignCenter )
row += 1
grid_l.addWidget(q_detected_faces, row, 0, 1, 4)
row += 1
super().__init__(backend, L('@QFaceDetector.module_title'), layout=lib_qt.QXVBoxLayout([grid_l]))
def _on_backend_state_change(self, backend, started, starting, stopping, stopped, busy):
super()._on_backend_state_change (backend, started, starting, stopping, stopped, busy)
if stopped:
self._q_face_coords_label.clear()
def _on_timer_10ms(self):
if self._q_detected_faces.is_opened() and not self.get_top_QXWindow().is_minimized():
bcd_id = self._bc_out.get_write_id()
if self._bcd_id != bcd_id:
# Has new bcd version
bcd, self._bcd_id = self._bc_out.get_by_id(bcd_id), bcd_id
if bcd is not None:
bcd.assign_weak_heap(self._weak_heap)
frame_name = bcd.get_frame_name()
frame_image = bcd.get_image(frame_name)
frame_image_w_h = None
if frame_image is not None:
h,w = frame_image.shape[0:2]
frame_image_w_h = (w,h)
info = []
for face_num,face_mark in enumerate(bcd.get_face_mark_list()):
info_str = f'{face_num}: '
rect = face_mark.get_face_urect()
if rect is not None:
l,t,r,b = rect.as_ltrb_bbox(frame_image_w_h).astype(np.int)
info_str += f'[{l},{t},{r},{b}]'
info.append(info_str)
info = '\n'.join(info)
self._q_face_coords_label.setText(info)

View file

@ -0,0 +1,61 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
QComboBoxCSWDynamicSingleSwitch
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
class QFaceMarker(QBackendPanel):
def __init__(self, backend):
cs = backend.get_control_sheet()
q_marker_type_label = QLabelPopupInfo(label=L('@QFaceMarker.marker_type'), popup_info_text=L('@QFaceMarker.help.marker_type') )
q_marker_type = QComboBoxCSWDynamicSingleSwitch(cs.marker_type, reflect_state_widgets=[q_marker_type_label])
q_device_label = QLabelPopupInfo(label=L('@QFaceMarker.device'), popup_info_text=L('@QFaceMarker.help.device') )
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
q_marker_coverage_label = QLabelPopupInfo(label=L('@QFaceMarker.marker_coverage'), popup_info_text=L('@QFaceMarker.help.marker_coverage') )
q_marker_coverage = QSpinBoxCSWNumber(cs.marker_coverage, reflect_state_widgets=[q_marker_coverage_label])
q_temporal_smoothing_label = QLabelPopupInfo(label=L('@QFaceMarker.temporal_smoothing'), popup_info_text=L('@QFaceMarker.help.temporal_smoothing') )
q_temporal_smoothing = QSpinBoxCSWNumber(cs.temporal_smoothing, reflect_state_widgets=[q_temporal_smoothing_label])
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_marker_type_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_marker_type, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_device_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_device, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
sub_row = 0
sub_grid_l = lib_qt.QXGridLayout(spacing=5)
sub_grid_l.addWidget(q_marker_coverage_label, sub_row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
sub_grid_l.addWidget(q_marker_coverage, sub_row, 1, 1, 1, alignment=Qt.AlignmentFlag.AlignLeft )
sub_row += 1
sub_grid_l.addWidget(q_temporal_smoothing_label, sub_row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
sub_grid_l.addWidget(q_temporal_smoothing, sub_row, 1, 1, 1, alignment=Qt.AlignmentFlag.AlignLeft )
sub_row += 1
grid_l.addLayout(sub_grid_l, row, 0, 1, 4, alignment=Qt.AlignmentFlag.AlignCenter )
row += 1
super().__init__(backend, L('@QFaceMarker.module_title'),
layout=lib_qt.QXVBoxLayout([grid_l]) )

View file

@ -0,0 +1,66 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
QComboBoxCSWDynamicSingleSwitch
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QSliderCSWNumber import QSliderCSWNumber
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
class QFaceMerger(QBackendPanel):
def __init__(self, backend):
cs = backend.get_control_sheet()
q_device_label = QLabelPopupInfo(label=L('@QFaceMerger.device'), popup_info_text=L('@QFaceMerger.help.device'))
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
q_face_x_offset_label = QLabelPopupInfo(label=L('@QFaceMerger.face_x_offset'))
q_face_x_offset = QSpinBoxCSWNumber(cs.face_x_offset, reflect_state_widgets=[q_face_x_offset_label])
q_face_y_offset_label = QLabelPopupInfo(label=L('@QFaceMerger.face_y_offset'))
q_face_y_offset = QSpinBoxCSWNumber(cs.face_y_offset, reflect_state_widgets=[q_face_y_offset_label])
q_face_scale_label = QLabelPopupInfo(label=L('@QFaceMerger.face_scale') )
q_face_scale = QSpinBoxCSWNumber(cs.face_scale, reflect_state_widgets=[q_face_scale_label])
q_face_mask_type_label = QLabelPopupInfo(label=L('@QFaceMerger.face_mask_type') )
q_face_mask_type = QComboBoxCSWDynamicSingleSwitch(cs.face_mask_type, reflect_state_widgets=[q_face_mask_type_label])
q_face_mask_erode_label = QLabelPopupInfo(label=L('@QFaceMerger.face_mask_erode') )
q_face_mask_erode = QSpinBoxCSWNumber(cs.face_mask_erode, reflect_state_widgets=[q_face_mask_erode_label])
q_face_mask_blur_label = QLabelPopupInfo(label=L('@QFaceMerger.face_mask_blur') )
q_face_mask_blur = QSpinBoxCSWNumber(cs.face_mask_blur, reflect_state_widgets=[q_face_mask_blur_label])
q_face_opacity_label = QLabelPopupInfo(label=L('@QFaceMerger.face_opacity') )
q_face_opacity = QSliderCSWNumber(cs.face_opacity, reflect_state_widgets=[q_face_opacity_label])
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_device_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_device, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addLayout( lib_qt.QXVBoxLayout([q_face_x_offset_label, q_face_y_offset_label]), row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addLayout( lib_qt.QXHBoxLayout([q_face_x_offset, q_face_y_offset]), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_face_scale_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_face_scale, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_face_mask_type_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_face_mask_type, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addLayout(lib_qt.QXVBoxLayout([q_face_mask_erode_label,q_face_mask_blur_label]), row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addLayout(lib_qt.QXHBoxLayout([q_face_mask_erode,q_face_mask_blur], spacing=3), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_face_opacity_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_face_opacity, row, 1)
row += 1
super().__init__(backend, L('@QFaceMerger.module_title'),
layout=lib_qt.QXVBoxLayout([grid_l]) )

View file

@ -0,0 +1,95 @@
from pathlib import Path
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.gfx import QXImageDB
from xlib import qt as lib_qt
from ..backend import FaceSwapper
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
QComboBoxCSWDynamicSingleSwitch
from .widgets.QErrorCSWError import QErrorCSWError
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QLabelPopupInfoCSWInfoLabel import QLabelPopupInfoCSWInfoLabel
from .widgets.QProgressBarCSWProgress import QProgressBarCSWProgress
from .widgets.QSliderCSWNumber import QSliderCSWNumber
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
class QFaceSwapper(QBackendPanel):
def __init__(self, backend : FaceSwapper, dfm_models_path : Path):
self._dfm_models_path = dfm_models_path
cs = backend.get_control_sheet()
btn_open_folder = self.btn_open_folder = lib_qt.QXPushButton(image = QXImageDB.eye_outline('light gray'), tooltip_text='Reveal in Explorer', released=self._btn_open_folder_released, fixed_size=(24,22) )
q_model_label = QLabelPopupInfo(label=L('@QFaceSwapper.model'), popup_info_text=L('@QFaceSwapper.help.model') )
q_model = QComboBoxCSWDynamicSingleSwitch(cs.model, reflect_state_widgets=[q_model_label, btn_open_folder])
q_model_dl_error = self._q_model_dl_error = QErrorCSWError(cs.model_dl_error)
q_model_dl_progress = self._q_model_dl_progress = QProgressBarCSWProgress(cs.model_dl_progress)
q_model_info_label = self._q_model_info_label = QLabelPopupInfoCSWInfoLabel(cs.model_info_label)
q_device_label = QLabelPopupInfo(label=L('@QFaceSwapper.device'), popup_info_text=L('@QFaceSwapper.help.device') )
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
q_face_id_label = QLabelPopupInfo(label=L('@QFaceSwapper.face_id'), popup_info_text=L('@QFaceSwapper.help.face_id') )
q_face_id = QSpinBoxCSWNumber(cs.face_id, reflect_state_widgets=[q_face_id_label])
q_morph_factor_label = QLabelPopupInfo(label=L('@QFaceSwapper.morph_factor'), popup_info_text=L('@QFaceSwapper.help.morph_factor') )
q_morph_factor = QSliderCSWNumber(cs.morph_factor, reflect_state_widgets=[q_morph_factor_label])
q_sharpen_amount_label = QLabelPopupInfo(label=L('@QFaceSwapper.presharpen_amount'), popup_info_text=L('@QFaceSwapper.help.presharpen_amount') )
q_sharpen_amount = QSliderCSWNumber(cs.presharpen_amount, reflect_state_widgets=[q_sharpen_amount_label])
q_pre_gamma_label = QLabelPopupInfo(label=L('@QFaceSwapper.pregamma'), popup_info_text=L('@QFaceSwapper.help.pregamma') )
q_pre_gamma_red = QSpinBoxCSWNumber(cs.pre_gamma_red, reflect_state_widgets=[q_pre_gamma_label])
q_pre_gamma_green = QSpinBoxCSWNumber(cs.pre_gamma_green )
q_pre_gamma_blue = QSpinBoxCSWNumber(cs.pre_gamma_blue)
q_two_pass_label = QLabelPopupInfo(label=L('@QFaceSwapper.two_pass'), popup_info_text=L('@QFaceSwapper.help.two_pass') )
q_two_pass = QCheckBoxCSWFlag(cs.two_pass, reflect_state_widgets=[q_two_pass_label])
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_model_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addLayout(lib_qt.QXHBoxLayout([q_model, 2, btn_open_folder, 2, q_model_info_label]), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_model_dl_progress, row, 0, 1, 2 )
row += 1
grid_l.addWidget(q_model_dl_error, row, 0, 1, 2 )
row += 1
grid_l.addWidget(q_device_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_device, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_face_id_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_face_id, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(q_morph_factor_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_morph_factor, row, 1)
row += 1
grid_l.addWidget(q_sharpen_amount_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_sharpen_amount, row, 1)
row += 1
grid_l.addWidget( q_pre_gamma_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addLayout( lib_qt.QXHBoxLayout([q_pre_gamma_red, q_pre_gamma_green, q_pre_gamma_blue ]), row, 1)
row += 1
grid_l.addWidget(q_two_pass_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_two_pass, row, 1)
row += 1
super().__init__(backend, L('@QFaceSwapper.module_title'),
layout=lib_qt.QXVBoxLayout([grid_l]) )
def _btn_open_folder_released(self):
QDesktopServices.openUrl(QUrl.fromLocalFile( str(self._dfm_models_path) ))

View file

@ -0,0 +1,77 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.gfx import QXImageDB
from xlib import qt as lib_qt
from ..backend import FileSource
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QButtonCSWDynamicSingleSwitch import \
QButtonCSWDynamicSingleSwitch
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
from .widgets.QErrorCSWError import QErrorCSWError
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QPathEditCSWPaths import QPathEditCSWPaths
from .widgets.QSliderCSWNumbers import QSliderCSWNumbers
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
class QFileSource(QBackendPanel):
def __init__(self, backend : FileSource):
cs = backend.get_control_sheet()
q_input_type = self._q_input_type = QButtonCSWDynamicSingleSwitch(cs.input_type, horizontal=True, radio_buttons=True)
q_input_paths = self._q_input_paths = QPathEditCSWPaths(cs.input_paths)
q_input_paths_error = self._q_input_paths_error = QErrorCSWError(cs.input_paths_error)
q_target_width_label = QLabelPopupInfo(label=L('@QFileSource.target_width'), popup_info_text=L('@QFileSource.help.target_width') )
q_target_width = self._q_target_width = QSpinBoxCSWNumber(cs.target_width, reflect_state_widgets=[q_target_width_label])
q_fps_label = QLabelPopupInfo(label=L('@QFileSource.fps'), popup_info_text=L('@QFileSource.help.fps') )
q_fps = self._q_fps = QSpinBoxCSWNumber(cs.fps, reflect_state_widgets=[q_fps_label])
q_is_realtime_label = QLabelPopupInfo(label=L('@QFileSource.is_realtime'), popup_info_text=L('@QFileSource.help.is_realtime') )
q_is_realtime = self._q_is_realtime = QCheckBoxCSWFlag(cs.is_realtime, reflect_state_widgets=[q_is_realtime_label])
q_is_autorewind_label = QLabelPopupInfo(label=L('@QFileSource.is_autorewind'))
q_is_autorewind = self._q_is_autorewind = QCheckBoxCSWFlag(cs.is_autorewind, reflect_state_widgets=[q_is_autorewind_label])
btn_size=(32,32)
btn_color= '#E01010'
btn_play = self._btn_play = QXPushButtonCSWSignal(cs.play, image=QXImageDB.play_circle_outline(btn_color), button_size=btn_size )
btn_pause = self._btn_pause = QXPushButtonCSWSignal(cs.pause, image=QXImageDB.pause_circle_outline(btn_color), button_size=btn_size )
btn_seek_backward = self._btn_seek_backward = QXPushButtonCSWSignal(cs.seek_backward, image=QXImageDB.play_back_circle_outline(btn_color), button_size=btn_size )
btn_seek_forward = self._btn_seek_forward = QXPushButtonCSWSignal(cs.seek_forward, image=QXImageDB.play_forward_circle_outline(btn_color), button_size=btn_size )
btn_seek_begin = self._btn_seek_begin = QXPushButtonCSWSignal(cs.seek_begin, image=QXImageDB.play_skip_back_circle_outline(btn_color), button_size=btn_size )
btn_seek_end = self._btn_seek_end = QXPushButtonCSWSignal(cs.seek_end, image=QXImageDB.play_skip_forward_circle_outline(btn_color), button_size=btn_size )
q_frame_slider = self._q_frame_slider = QSliderCSWNumbers(cs.frame_index, cs.frame_count)
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_target_width_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_target_width, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addLayout( lib_qt.QXHBoxLayout([q_is_realtime_label, 5, q_is_realtime, 5, q_fps_label]), row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget( q_fps, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget( q_is_autorewind_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget( q_is_autorewind, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
row += 1
grid_l.addWidget(lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([btn_seek_begin, btn_seek_backward, btn_play, btn_pause,btn_seek_forward, btn_seek_end], spacing=1), ),
row, 0, 1, 4, alignment=Qt.AlignmentFlag.AlignCenter)
row += 1
grid_l.addWidget(q_frame_slider, row, 0, 1, 4)
row += 1
main_l = lib_qt.QXVBoxLayout([q_input_type,
q_input_paths,
q_input_paths_error,
grid_l
], spacing=5)
super().__init__(backend, L('@QFileSource.module_title'),
layout=main_l, content_align_top=True)

View file

@ -0,0 +1,33 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QSliderCSWNumber import QSliderCSWNumber
class QFrameAdjuster(QBackendPanel):
def __init__(self, backend):
cs = backend.get_control_sheet()
q_median_blur_label = QLabelPopupInfo(label=L('@QFrameAdjuster.median_blur_per'), popup_info_text=L('@QFrameAdjuster.help.median_blur_per') )
q_median_blur = QSliderCSWNumber(cs.median_blur_per, reflect_state_widgets=[q_median_blur_label])
q_degrade_bicubic_label = QLabelPopupInfo(label=L('@QFrameAdjuster.degrade_bicubic_per'), popup_info_text=L('@QFrameAdjuster.help.degrade_bicubic_per') )
q_degrade_bicubic = QSliderCSWNumber(cs.degrade_bicubic_per, reflect_state_widgets=[q_degrade_bicubic_label])
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_median_blur_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_median_blur, row, 1)
row += 1
grid_l.addWidget(q_degrade_bicubic_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_degrade_bicubic, row, 1)
row += 1
super().__init__(backend, L('@QFrameAdjuster.module_title'),
layout=lib_qt.QXVBoxLayout([grid_l]) )

View file

@ -0,0 +1,72 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from ..backend import StreamOutput
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
QComboBoxCSWDynamicSingleSwitch
from .widgets.QErrorCSWError import QErrorCSWError
from .widgets.QLabelCSWNumber import QLabelCSWNumber
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QPathEditCSWPaths import QPathEditCSWPaths
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
class QStreamOutput(QBackendPanel):
def __init__(self, backend : StreamOutput):
cs = backend.get_control_sheet()
q_average_fps_label = QLabelPopupInfo(label=L('@QStreamOutput.avg_fps'), popup_info_text=L('@QStreamOutput.help.avg_fps'))
q_average_fps = QLabelCSWNumber(cs.avg_fps, reflect_state_widgets=[q_average_fps_label])
q_source_type_label = QLabelPopupInfo(label=L('@QStreamOutput.source_type') )
q_source_type = QComboBoxCSWDynamicSingleSwitch(cs.source_type, reflect_state_widgets=[q_source_type_label])
q_show_hide_window = QXPushButtonCSWSignal(cs.show_hide_window, text=L('@QStreamOutput.show_hide_window'), button_height=22)
q_aligned_face_id_label = QLabelPopupInfo(label=L('@QStreamOutput.aligned_face_id'), popup_info_text=L('@QStreamOutput.help.aligned_face_id'))
q_aligned_face_id = QSpinBoxCSWNumber(cs.aligned_face_id, reflect_state_widgets=[q_aligned_face_id_label])
q_target_delay_label = QLabelPopupInfo(label=L('@QStreamOutput.target_delay'), popup_info_text=L('@QStreamOutput.help.target_delay'))
q_target_delay = QSpinBoxCSWNumber(cs.target_delay, reflect_state_widgets=[q_target_delay_label])
q_save_sequence_path_label = QLabelPopupInfo(label=L('@QStreamOutput.save_sequence_path'), popup_info_text=L('@QStreamOutput.help.save_sequence_path'))
q_save_sequence_path = QPathEditCSWPaths(cs.save_sequence_path, reflect_state_widgets=[q_target_delay_label])
q_save_sequence_path_error = QErrorCSWError(cs.save_sequence_path_error)
q_save_fill_frame_gap_label = QLabelPopupInfo(label=L('@QStreamOutput.save_fill_frame_gap'), popup_info_text=L('@QStreamOutput.help.save_fill_frame_gap'))
q_save_fill_frame_gap = QCheckBoxCSWFlag(cs.save_fill_frame_gap, reflect_state_widgets=[q_save_fill_frame_gap_label])
grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0
grid_l.addWidget(q_average_fps_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_average_fps, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
row += 1
grid_l.addWidget(q_source_type_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_source_type, row, 1, 1, 1, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_show_hide_window, row, 2, 1, 1)
row += 1
grid_l.addWidget(q_aligned_face_id_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_aligned_face_id, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
row += 1
grid_l.addWidget(q_target_delay_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_target_delay, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
row += 1
grid_l.addWidget(q_save_sequence_path_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
grid_l.addWidget(q_save_sequence_path, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
row += 1
grid_l.addLayout( lib_qt.QXHBoxLayout([q_save_fill_frame_gap, 4, q_save_fill_frame_gap_label]), row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
row += 1
grid_l.addWidget(q_save_sequence_path_error, row, 0, 1, 3)
row += 1
super().__init__(backend, L('@QStreamOutput.module_title'),
layout=grid_l)

View file

@ -0,0 +1,86 @@
import numpy as np
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from xlib.facemeta import FaceULandmarks
from xlib.python import all_is_not_None
from ... import backend
class QBCFaceAlignViewer(lib_qt.QXCollapsibleSection):
"""
"""
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
bc : backend.BackendConnection,
preview_width=256,):
self._preview_width = preview_width
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
self._backed_weak_heap = backed_weak_heap
self._bc = bc
self._bcd_id = None
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
super().__init__(title=L('@QBCFaceAlignViewer.title'),
content_layout=lib_qt.QXVBoxLayout(
[ (layered_images, Qt.AlignmentFlag.AlignCenter),
(info_label, Qt.AlignmentFlag.AlignCenter),
]) )
def _on_timer_8ms(self):
top_qx = self.get_top_QXWindow()
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
return
bcd_id = self._bc.get_write_id()
if self._bcd_id != bcd_id:
# Has new bcd version
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
if bcd is not None:
bcd.assign_weak_heap(self._backed_weak_heap)
self._layered_images.clear_images()
for face_mark in bcd.get_face_mark_list():
face_align = face_mark.get_face_align()
if face_align is not None:
face_align_image_name = face_align.get_image_name()
source_to_aligned_uni_mat = face_align.get_source_to_aligned_uni_mat()
if all_is_not_None(face_align_image_name):
face_image = bcd.get_image(face_align_image_name).copy()
face_ulmrks = face_align.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_468)
if face_ulmrks is None:
face_ulmrks = face_align.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_68)
if face_ulmrks is not None:
lmrks_layer = np.zeros( (self._preview_width, self._preview_width, 4), dtype=np.uint8)
face_ulmrks.draw(lmrks_layer, (0,255,0,255))
face_mark_rect = face_mark.get_face_urect()
if face_mark_rect is not None:
aligned_uni_rect = face_mark_rect.transform(source_to_aligned_uni_mat)
aligned_uni_rect.draw(lmrks_layer, (0,0,255,255) )
self._layered_images.add_image(face_image)
self._layered_images.add_image(lmrks_layer)
h,w = face_image.shape[0:2]
self._info_label.setText(f'{w}x{h}')
return
def clear(self):
self._layered_images.clear_images()

View file

@ -0,0 +1,61 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from ... import backend
class QBCFaceSwapViewer(lib_qt.QXCollapsibleSection):
"""
"""
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
bc : backend.BackendConnection,
preview_width=256,):
self._preview_width = preview_width
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
self._backed_weak_heap = backed_weak_heap
self._bc = bc
self._bcd_id = None
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
main_l = lib_qt.QXVBoxLayout([ (layered_images, Qt.AlignmentFlag.AlignCenter),
(info_label, Qt.AlignmentFlag.AlignCenter),
], spacing=0)
super().__init__(title=L('@QBCFaceSwapViewer.title'), content_layout=main_l)
def _on_timer_8ms(self):
top_qx = self.get_top_QXWindow()
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
return
bcd_id = self._bc.get_write_id()
if self._bcd_id != bcd_id:
# Has new bcd version
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
if bcd is not None:
bcd.assign_weak_heap(self._backed_weak_heap)
self._layered_images.clear_images()
for face_mark in bcd.get_face_mark_list():
face_align = face_mark.get_face_align()
if face_align is not None:
face_swap = face_align.get_face_swap()
if face_swap is not None:
face_swap_image = bcd.get_image(face_swap.get_image_name())
if face_swap_image is not None:
self._layered_images.add_image(face_swap_image)
h,w = face_swap_image.shape[0:2]
self._info_label.setText(f'{w}x{h}')
return
def clear(self):
self._layered_images.clear_images()

View file

@ -0,0 +1,60 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from ... import backend
class QBCFinalFrameViewer(lib_qt.QXCollapsibleSection):
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
bc : backend.BackendConnection,
preview_width=256):
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
self._backed_weak_heap = backed_weak_heap
self._bc = bc
self._bcd_id = None
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
main_l = lib_qt.QXVBoxLayout([ (layered_images, Qt.AlignmentFlag.AlignCenter),
(info_label, Qt.AlignmentFlag.AlignCenter),
], spacing=0)
super().__init__(title=L('@QBCFinalFrameViewer.title'), content_layout=main_l)
def _on_timer_8ms(self):
top_qx = self.get_top_QXWindow()
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
return
bcd_id = self._bc.get_write_id()
if self._bcd_id != bcd_id:
# Has new bcd version
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
if bcd is not None:
bcd.assign_weak_heap(self._backed_weak_heap)
self._layered_images.clear_images()
merged_frame_name = bcd.get_merged_frame_name()
merged_frame_image = bcd.get_image(merged_frame_name)
if merged_frame_name is not None and merged_frame_image is not None:
self._layered_images.add_image(merged_frame_image)
h,w = merged_frame_image.shape[0:2]
if merged_frame_name is not None:
self._info_label.setText(f'{merged_frame_name} {w}x{h}')
else:
self._info_label.setText(f'{w}x{h}')
def clear(self):
self._layered_images.clear_images()

View file

@ -0,0 +1,59 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from ... import backend
class QBCFrameViewer(lib_qt.QXCollapsibleSection):
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
bc : backend.BackendConnection,
preview_width=256):
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
self._backed_weak_heap = backed_weak_heap
self._bc = bc
self._bcd_id = None
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
main_l = lib_qt.QXVBoxLayout([ (layered_images, Qt.AlignmentFlag.AlignCenter),
(info_label, Qt.AlignmentFlag.AlignCenter),
])
super().__init__(title=L('@QBCFrameViewer.title'), content_layout=main_l)
def _on_timer_8ms(self):
top_qx = self.get_top_QXWindow()
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
return
bcd_id = self._bc.get_write_id()
if self._bcd_id != bcd_id:
# Has new bcd version
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
if bcd is not None:
bcd.assign_weak_heap(self._backed_weak_heap)
self._layered_images.clear_images()
frame_name = bcd.get_frame_name()
frame_image = bcd.get_image(frame_name)
if frame_image is not None:
self._layered_images.add_image(frame_image)
h,w = frame_image.shape[0:2]
if frame_name is not None:
self._info_label.setText(f'{frame_name} {w}x{h}')
else:
self._info_label.setText(f'{w}x{h}')
def clear(self):
self._layered_images.clear_images()

View file

@ -0,0 +1,104 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from resources.gfx import QXImageDB, QXImageSequenceDB
from xlib import qt as lib_qt
from ...backend import BackendHost
class QBackendPanel(lib_qt.QXWidget):
"""
Base panel for CSW backend
"""
def __init__(self, backend : BackendHost, name : str, layout, content_align_top=False):
if not isinstance(backend, BackendHost):
raise ValueError('backend must be an instance of BackendHost')
super().__init__()
self._backend = backend
self._name = name
backend.call_on_state_change(self._on_backend_state_change)
backend.call_on_profile_timing(self._on_backend_profile_timing)
btn_on_off = self._btn_on_off = lib_qt.QXPushButton(tooltip_text=L('@QBackendPanel.start'), fixed_width=20, released=self._on_btn_on_off_released)
btn_reset_state = self._btn_reset_state = lib_qt.QXPushButton(image=QXImageDB.settings_reset_outline('gray'),
#size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum),
fixed_width=20, released=self._on_btn_reset_state_released, tooltip_text=L('@QBackendPanel.reset_settings') )
timing_label = self._timing_label = lib_qt.QXLabel()
bar_widget = self._bar_widget = \
lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([
btn_on_off,
1,
btn_reset_state,
2,
lib_qt.QXLabel(name, font=QXFontDB.get_default_font(10)),
(timing_label, Qt.AlignmentFlag.AlignRight),
2
]), fixed_height=24),
]),
size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) )
content_widget = self._content_widget = lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([layout], contents_margins=2),
enabled=False )
l_widgets = [bar_widget, 1]
if not content_align_top:
l_widgets += [ lib_qt.QXFrame(size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) ) ]
l_widgets += [content_widget]
l_widgets += [ lib_qt.QXFrame(size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) ) ]
self.setLayout(lib_qt.QXVBoxLayout(l_widgets))
lib_qt.disable([self._content_widget])
btn_on_off.set_image( QXImageDB.power_outline('red') )
def _on_backend_state_change(self, backend, started, starting, stopping, stopped, busy):
btn_on_off = self._btn_on_off
if started or starting or stopping:
btn_on_off.setToolTip(L('@QBackendPanel.stop'))
if stopped:
btn_on_off.setToolTip(L('@QBackendPanel.start'))
if busy or starting or stopping:
btn_on_off.set_image_sequence(QXImageSequenceDB.icon_loading('yellow'), loop_count=0)
elif started:
btn_on_off.set_image( QXImageDB.power_outline('lime') )
elif stopped:
btn_on_off.set_image( QXImageDB.power_outline('red') )
if started and not busy:
lib_qt.show_and_enable([self._content_widget, self._timing_label])
self._timing_label.setText(None)
else:
lib_qt.hide_and_disable([self._content_widget, self._timing_label])
self._timing_label.setText(None)
def _on_backend_profile_timing(self, timing : float):
fps = int(1.0 / timing if timing != 0 else 0)
if fps < 10:
self._timing_label.set_color('red')
else:
self._timing_label.set_color(None)
self._timing_label.setText(f"{fps} {L('@QBackendPanel.FPS')}")
def _on_btn_on_off_released(self):
backend = self._backend
if backend.is_stopped():
backend.start()
else:
backend.stop()
def _on_btn_reset_state_released(self):
self._backend.reset_state()

View file

@ -0,0 +1,67 @@
from typing import Union
from localization import Localization
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QButtonCSWDynamicSingleSwitch(QCSWControl):
"""
Implements lib_csw.DynamicSingleSwitch control with radiobuttons or checkboxes
"""
def __init__(self, csw_switch : lib_csw.DynamicSingleSwitch.Client,
horizontal : bool, radio_buttons : bool):
if not isinstance(csw_switch, lib_csw.DynamicSingleSwitch.Client):
raise ValueError('csw_switch must be an instance of DynamicSingleSwitch.Client')
super().__init__(csw_control=csw_switch)
self._csw_switch = csw_switch
self._is_radio_buttons = radio_buttons
csw_switch.call_on_selected(self._on_csw_switch_selected)
csw_switch.call_on_choices(self._on_csw_choices)
self._btns = []
main_l = self._main_l = lib_qt.QXHBoxLayout() if horizontal else lib_qt.QXVBoxLayout()
self.setLayout(lib_qt.QXHBoxLayout([ lib_qt.QXFrame(layout=main_l, size_policy=(QSizePolicy.Policy.Fixed,QSizePolicy.Policy.Fixed) ), ]) )
self.hide()
def _on_csw_choices(self, choices, choices_names, none_choice_name : Union[str,None]):
for btn in self._btns:
self._main_l.removeWidget(btn)
btn.deleteLater()
btns = self._btns = []
for idx, choice in enumerate(choices_names):
choice = Localization.localize(choice)
if self._is_radio_buttons:
btn = lib_qt.QXRadioButton(text=choice, toggled=(lambda checked, idx=idx: self.on_btns_toggled(idx, checked)) )
else:
btn = lib_qt.QXCheckBox(text=choice, toggled=(lambda checked, idx=idx: self.on_btns_toggled(idx, checked)) )
btns.append(btn)
self._main_l.addWidget(btn, alignment=Qt.AlignmentFlag.AlignLeft)
def on_btns_toggled(self, idx, checked):
if checked:
if not self._csw_switch.select(idx):
with lib_qt.BlockSignals(self._btns[idx]):
self._btns[idx].setChecked(False)
else:
if not self._csw_switch.unselect():
with lib_qt.BlockSignals(self._btns[idx]):
self._btns[idx].setChecked(True)
def _on_csw_switch_selected(self, idx, choice):
with lib_qt.BlockSignals(self._btns):
for i,btn in enumerate(self._btns):
btn.setChecked(i==idx)

View file

@ -0,0 +1,55 @@
from collections import Iterable
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
class QCSWControl(lib_qt.QXWidget):
"""
base qt widget class for CSWControl
"""
def __init__(self, csw_control : lib_csw.Control, reflect_state_widgets=None):
super().__init__()
self._csw_control = csw_control
self._csw_state_widgets = []
csw_control.call_on_change_state(self._on_csw_state_change)
if reflect_state_widgets is not None:
self.reflect_state_to_widget(reflect_state_widgets)
def _on_csw_state_change(self, state):
"""overridable"""
self._state_to_widget( self._csw_state_widgets+[self] )
def reflect_state_to_widget(self, widget_or_list):
"""reflect CSW Control state to widgets"""
if isinstance(widget_or_list, Iterable):
widget_or_list = list(widget_or_list)
else:
widget_or_list = [widget_or_list]
self._csw_state_widgets += widget_or_list
self._state_to_widget(widget_or_list)
def _state_to_widget(self, widgets):
state = self._csw_control.get_state()
if state == lib_csw.Control.State.ENABLED:
for widget in widgets:
widget.show()
widget.setEnabled(True)
elif state == lib_csw.Control.State.FREEZED:
for widget in widgets:
widget.setEnabled(False)
widget.show()
elif state == lib_csw.Control.State.DISABLED:
for widget in widgets:
widget.setEnabled(False)
widget.hide()

View file

@ -0,0 +1,34 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QCheckBoxCSWFlag(QCSWControl):
"""
Implements lib_csw.Flag control as CheckBox
"""
def __init__(self, csw_flag : lib_csw.Flag.Client, reflect_state_widgets=None):
if not isinstance(csw_flag, lib_csw.Flag.Client):
raise ValueError('csw_flag must be an instance of Flag.Client')
super().__init__(csw_control=csw_flag, reflect_state_widgets=reflect_state_widgets)
self._csw_flag = csw_flag
csw_flag.call_on_flag(self.on_csw_flag)
# Init UI
chbox = self._chbox = lib_qt.QXCheckBox(clicked=self.on_chbox_clicked)
main_l = lib_qt.QXHBoxLayout([chbox])
self.setLayout(main_l)
self.hide()
def on_csw_flag(self, flag):
with lib_qt.BlockSignals(self._chbox):
self._chbox.setChecked(flag)
def on_chbox_clicked(self):
self._csw_flag.set_flag(self._chbox.isChecked())

View file

@ -0,0 +1,71 @@
from typing import Union
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QComboBoxCSWDynamicSingleSwitch(QCSWControl):
"""
Implements lib_csw.DynamicSingleSwitch control with QComboBox
"""
def __init__(self, csw_switch : lib_csw.DynamicSingleSwitch.Client, reflect_state_widgets=None):
if not isinstance(csw_switch, lib_csw.DynamicSingleSwitch.Client):
raise ValueError('csw_switch must be an instance of DynamicSingleSwitch.Client')
super().__init__(csw_control=csw_switch, reflect_state_widgets=reflect_state_widgets)
self._csw_switch = csw_switch
self._has_none_choice = True
csw_switch.call_on_selected(self._on_csw_switch_selected)
csw_switch.call_on_choices(self._on_csw_choices)
# Init UI
self._combobox = None
main_l = self._main_l = lib_qt.QXHBoxLayout()
self.setLayout(main_l)
self.hide()
def _on_csw_choices(self, choices, choices_names, none_choice_name : Union[str,None]):
if self._combobox is not None:
self._main_l.removeWidget(self._combobox)
self._combobox.deleteLater()
self._choices_names = choices_names
self._has_none_choice = none_choice_name is not None
combobox = self._combobox = lib_qt.QXComboBox(font=QXFontDB.get_fixedwidth_font(), on_index_changed=self.on_combobox_index_changed)
with lib_qt.BlockSignals(self._combobox):
if none_choice_name is not None:
combobox.addItem( QIcon(), L(none_choice_name) )
for choice_name in choices_names:
combobox.addItem( QIcon(), L(choice_name) )
self._main_l.addWidget(combobox)
def on_combobox_index_changed(self, idx):
if self._has_none_choice and idx == 0:
self._csw_switch.unselect()
else:
if self._has_none_choice:
idx -= 1
if not self._csw_switch.select(idx):
raise Exception('error on select')
def _on_csw_switch_selected(self, idx, choice):
with lib_qt.BlockSignals(self._combobox):
if idx is None:
self._combobox.setCurrentIndex(0)
else:
if self._has_none_choice:
idx += 1
self._combobox.setCurrentIndex(idx)

View file

@ -0,0 +1,47 @@
from typing import Union
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from resources.gfx import QXImageDB
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QErrorCSWError(QCSWControl):
"""
Implements lib_csw.Error control as widget
"""
def __init__(self, csw_error : lib_csw.Error.Client):
if not isinstance(csw_error, lib_csw.Error.Client):
raise ValueError('csw_error must be an instance of Error.Client')
super().__init__(csw_control=csw_error)
self._csw_error = csw_error
csw_error.call_on_error(self._on_csw_error)
label_warning = self._label_warning = lib_qt.QXLabel(image=QXImageDB.warning_outline('red'),
scaled_contents=True,
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed),
fixed_size=(32,32),
)
label = self._label = lib_qt.QXLabel(font=QXFontDB.get_default_font(size=7), word_wrap=True)
bar = lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout(
[ lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([label_warning]), size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)) ,
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([label]))
], spacing=0) )
self.setLayout(lib_qt.QXHBoxLayout([bar]))
self.hide()
def _on_csw_state_change(self, state):
super()._on_csw_state_change(state)
if state == lib_csw.Control.State.DISABLED:
self._label.setText(None)
def _on_csw_error(self, text: Union[str,None]):
self._label.setText(text)

View file

@ -0,0 +1,44 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QLabelCSWNumber(QCSWControl):
"""
Implements lib_csw.Number control as QXLabel read-only
"""
def __init__(self, csw_number : lib_csw.Number.Client, reflect_state_widgets=None):
if not isinstance(csw_number, lib_csw.Number.Client):
raise ValueError('csw_number must be an instance of Number.Client')
super().__init__(csw_control=csw_number, reflect_state_widgets=reflect_state_widgets)
self._csw_number = csw_number
self._decimals = 0
csw_number.call_on_number(self._on_csw_number)
csw_number.call_on_config(self._on_csw_config)
label = self._label = lib_qt.QXLabel( font=QXFontDB.Digital7_Mono(11, italic=True) )
main_l = lib_qt.QXHBoxLayout([label])
self.setLayout(main_l)
self.hide()
def _on_csw_number(self, number):
f = (10**self._decimals)
number = int(number * f) / f
self._label.setText(str(number))
def _on_csw_config(self, cfg : lib_csw.Number.Config):
if not cfg.read_only:
raise Exception('QLabelCSWNumber supports only read-only Number')
if cfg.decimals is not None:
self._decimals = cfg.decimals

View file

@ -0,0 +1,101 @@
from typing import Union
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from resources.gfx import QXImageDB
from xlib import qt as lib_qt
class QLabelPopupInfo(lib_qt.QXWidget):
"""
text label with optional popup info on click
"""
def __init__(self, label : str = None, popup_info_text = None):
super().__init__()
self._has_info_text = False
self._label = lib_qt.QXLabel(text='')
self._label.hide()
wnd = self._popup_wnd = lib_qt.QXWindow()
wnd.setParent(self)
wnd.setWindowFlags(Qt.WindowType.Popup)
info_btn = self._info_btn = lib_qt.QXPushButton(image=QXImageDB.information_circle_outline('light gray'), fixed_size=(24,22), released=self._on_info_btn_released)
info_btn.hide()
wnd_text_label = self._popup_wnd_text_label = lib_qt.QXLabel(text='', font=QXFontDB.get_default_font() )
wnd_layout = lib_qt.QXHBoxLayout([
lib_qt.QXFrame(
bg_color= Qt.GlobalColor.black,
layout=lib_qt.QXHBoxLayout([
lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([lib_qt.QXLabel(image=QXImageDB.information_circle_outline('yellow'), scaled_contents=True, fixed_size=(24,24)),
wnd_text_label
], contents_margins=2, spacing=2)),
], contents_margins=2, spacing=2), size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) )
], contents_margins=0)
wnd.setLayout(wnd_layout)
self.setLayout(lib_qt.QXHBoxLayout([self._label, info_btn]))
self.set_label( label )
self.set_popup_info( popup_info_text )
def set_info_icon(self):
self._label.hide()
self._info_btn.show()
def set_label(self, label : Union[str, None]):
self._info_btn.hide()
self._label.setText(label)
self._label.show()
def set_popup_info(self, text : Union[str, None]):
if text is not None:
self._has_info_text = True
self._popup_wnd_text_label.setText(text)
else:
self._has_info_text = False
def enterEvent(self, ev):
super().enterEvent(ev)
if self.isEnabled() and self._has_info_text:
self._label.set_color('yellow')
def leaveEvent(self, ev):
super().leaveEvent(ev)
if self.isEnabled() and self._has_info_text:
self._label.set_color(None)
def mousePressEvent(self, ev):
super().mousePressEvent(ev)
self._show_popup_wnd()
def _on_info_btn_released(self):
self._show_popup_wnd()
def _show_popup_wnd(self):
if self._has_info_text:
popup_wnd = self._popup_wnd
popup_wnd.show()
label_widget = self._label
if label_widget.isHidden():
label_widget = self._info_btn
screen_size = lib_qt.QXMainApplication.get_singleton().primaryScreen().size()
label_size = label_widget.size()
global_pt = label_widget.mapToGlobal( QPoint(0, label_size.height()))
popup_wnd_size = popup_wnd.size()
global_pt = QPoint( min(global_pt.x(), screen_size.width() - popup_wnd_size.width()),
min(global_pt.y(), screen_size.height() - popup_wnd_size.height()) )
popup_wnd.move(global_pt)

View file

@ -0,0 +1,45 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
from .QLabelPopupInfo import QLabelPopupInfo
class QLabelPopupInfoCSWInfoLabel(QCSWControl):
"""
Implements lib_csw.InfoLabel control as QLabelPopupInfo
"""
def __init__(self, csw_info_label : lib_csw.InfoLabel.Client):
if not isinstance(csw_info_label, lib_csw.InfoLabel.Client):
raise ValueError('csw_error must be an instance of InfoLabel.Client')
super().__init__(csw_control=csw_info_label)
self._csw_info_label = csw_info_label
csw_info_label.call_on_config(self._on_csw_config)
label_popup_info = self._label_popup_info = QLabelPopupInfo()
self.setLayout(lib_qt.QXHBoxLayout([label_popup_info]))
self.hide()
def _on_csw_state_change(self, state):
super()._on_csw_state_change(state)
if state == lib_csw.Control.State.DISABLED:
self._label_popup_info.set_label(None)
self._label_popup_info.set_popup_info(None)
def _on_csw_config(self, cfg : lib_csw.InfoLabel.Config ):
if cfg.info_icon:
self._label_popup_info.set_info_icon()
else:
self._label_popup_info.set_label(L(cfg.label))
self._label_popup_info.set_popup_info( '\n'.join([L(line) for line in cfg.info_lines])
if cfg.info_lines is not None else None)

View file

@ -0,0 +1,131 @@
from pathlib import Path
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from resources.gfx import QXImageDB
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QPathEditCSWPaths(QCSWControl):
"""
Implements lib_csw.Paths control as LineEdit with Button to manage the Path
"""
def __init__(self, csw_path : lib_csw.Paths.Client, reflect_state_widgets=None):
if not isinstance(csw_path, lib_csw.Paths.Client):
raise ValueError('csw_path must be an instance of Paths.Client')
super().__init__(csw_control=csw_path, reflect_state_widgets=reflect_state_widgets)
self._csw_path = csw_path
self._dlg = None
csw_path.call_on_config(self.on_csw_config)
csw_path.call_on_paths(self._on_csw_paths)
lineedit = self._lineedit = lib_qt.QXLineEdit(font=QXFontDB.get_fixedwidth_font(),
placeholder_text='...',
size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed),
editingFinished=self.on_lineedit_editingFinished)
btn_open = self._btn_open = lib_qt.QXPushButton(image=QXImageDB.folder_open_outline(color='light gray'),
tooltip_text='Open',
released=self._on_btn_open_released,
fixed_size=(24,22) )
btn_reveal = self._btn_reveal = lib_qt.QXPushButton(image=QXImageDB.eye_outline(color='light gray'),
tooltip_text='Reveal in explorer',
released=self._on_btn_reveal_released,
fixed_size=(24,22) )
btn_erase = self._btn_erase = lib_qt.QXPushButton(image=QXImageDB.close_outline(color='light gray'),
tooltip_text='Erase',
released=self._on_btn_erase_released,
fixed_size=(24,22) )
main_l = lib_qt.QXHBoxLayout([lineedit, 2, btn_open, btn_reveal,btn_erase])
self.setLayout(lib_qt.QXHBoxLayout([
lib_qt.QXFrame(layout=main_l, size_policy=(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Fixed) )
]) )
self.hide()
def on_csw_config(self, cfg : lib_csw.Paths.Config):
type = cfg.get_type()
caption = cfg.get_caption()
if caption is None:
caption = ""
suffixes = cfg.get_suffixes()
is_save = cfg.is_save()
file_filter = None
if suffixes is not None:
file_filter = f"{caption} ({' '.join(['*'+suf for suf in suffixes])})"
if type == lib_csw.Paths.Config.Type.ANY_FILE:
self._dlg = lib_qt.QXFileDialog(self,
filter=file_filter,
existing_only=False,
is_save=is_save,
accepted=self._on_dlg_accepted)
elif type == lib_csw.Paths.Config.Type.EXISTING_FILE:
self._dlg = lib_qt.QXFileDialog(self,
filter=file_filter,
existing_only=True,
is_save=is_save,
accepted=self._on_dlg_accepted)
elif type == lib_csw.Paths.Config.Type.EXISTING_FILES:
self._dlg = lib_qt.QXFileDialog(self,
multi_files=True,
existing_only=True,
filter=file_filter,
accepted=self._on_dlg_accepted)
elif type == lib_csw.Paths.Config.Type.DIRECTORY:
directory = cfg.get_directory_path()
if directory is not None:
directory = str(directory)
self._dlg = lib_qt.QXDirDialog(self, caption=caption, directory=directory, accepted=self._on_dlg_accepted)
def _on_dlg_accepted(self):
self._lineedit.setText( self._dlg.selectedFiles()[0] )
self._lineedit.editingFinished.emit()
def _on_csw_paths(self, paths, prev_paths):
if len(paths) == 0:
text = None
elif len(paths) == 1:
text = str(paths[0])
else:
raise NotImplementedError()
with lib_qt.BlockSignals(self._lineedit):
self._lineedit.setText(text)
def _on_btn_erase_released(self):
self._csw_path.set_paths(None)
def _on_btn_open_released(self):
if self._dlg is not None:
self._dlg.open()
else:
print('lib_csw.Paths.Config was not initialized.')
def _on_btn_reveal_released(self):
dirpath = self._lineedit.text()
if dirpath is not None and len(dirpath) != 0:
dirpath = Path(dirpath)
if dirpath.is_file():
dirpath = dirpath.parent
QDesktopServices.openUrl( QUrl.fromLocalFile(str(dirpath)) )
def on_lineedit_editingFinished(self):
text = self._lineedit.text()
if len(text) == 0:
text = None
self._csw_path.set_paths( text )

View file

@ -0,0 +1,47 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QProgressBarCSWProgress(QCSWControl):
"""
"""
def __init__(self, csw_progress : lib_csw.Progress.Client):
if not isinstance(csw_progress, lib_csw.Progress.Client):
raise ValueError('csw_progress must be an instance of Progress.Client')
super().__init__(csw_control=csw_progress)
self._csw_progress = csw_progress
csw_progress.call_on_progress(self._on_csw_progress)
csw_progress.call_on_config(self._on_csw_config)
label_title = self._label_title = lib_qt.QXLabel('', word_wrap=True, hided=True)
progressbar = self._progressbar = lib_qt.QXProgressBar( min=0, max=100, font=QXFontDB.Digital7_Mono(11, italic=True) ) # ,, step=1, decimals=0, readonly=self._read_only, valueChanged=self._on_spinbox_valueChanged, editingFinished=self._on_spinbox_editingFinished)
self.setLayout( lib_qt.QXVBoxLayout([label_title, progressbar]) )
self.hide()
def _on_csw_progress(self, progress):
with lib_qt.BlockSignals(self._progressbar):
self._progressbar.setValue(progress)
def _on_csw_config(self, config : lib_csw.Progress.Config):
title = config.get_title()
if title is not None:
if len(title) != 0 and title[0] == '@':
title = L(title[1:])
self._label_title.setText(title)
self._label_title.show()
else:
self._label_title.hide()

View file

@ -0,0 +1,75 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QSliderCSWNumber(QCSWControl):
"""
Implements lib_csw.Number control by Slider
"""
def __init__(self, csw_number: lib_csw.Number.Client, reflect_state_widgets=None):
if not isinstance(csw_number, lib_csw.Number.Client):
raise ValueError('csw_number must be an instance of Number.Client')
super().__init__(csw_control=csw_number, reflect_state_widgets=reflect_state_widgets)
self._csw_number = csw_number
csw_number.call_on_number(self._on_csw_number)
csw_number.call_on_config(self._on_csw_config)
slider = self._slider = lib_qt.QXSlider(orientation=Qt.Orientation.Horizontal,
min=0,
max=0,
tick_position=QSlider.TickPosition.NoTicks,
tick_interval=1,
sliderReleased=self._on_slider_sliderReleased,
valueChanged=self._on_slider_valueChanged)
self.setLayout(lib_qt.QXVBoxLayout([slider]))
self.hide()
def _on_csw_config(self, config : lib_csw.Number.Config):
self._config = config
if config.min is not None and config.max is not None:
min = config.min
max = config.max
step = config.step
int_min = 0
int_max = int( (max-min) / step )
self._slider.setMinimum(int_min)
self._slider.setMaximum(int_max)
self._slider.setPageStep(1)
self._slider.setEnabled(not config.read_only)
self._instant_update = config.allow_instant_update
def _on_csw_number(self, value):
if value is not None:
config = self._config
value = (value-config.min) / config.step
with lib_qt.BlockSignals([self._slider]):
self._slider.setValue(value)
def _set_csw_value(self):
config = self._config
value = self._slider.value()
value = value * config.step + config.min
self._csw_number.set_number(value)
def _on_slider_sliderReleased(self):
if not self._config.allow_instant_update:
self._set_csw_value()
def _on_slider_valueChanged(self):
if not self._slider.isSliderDown() or self._config.allow_instant_update:
self._set_csw_value()

View file

@ -0,0 +1,74 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QSliderCSWNumbers(QCSWControl):
"""
Implements controlable Slider with lib_csw.Number controls
"""
def __init__(self, csw_idx : lib_csw.Number.Client,
csw_idx_count : lib_csw.Number.Client,
):
if not isinstance(csw_idx, lib_csw.Number.Client):
raise ValueError('csw_idx must be an instance of Number.Client')
if not isinstance(csw_idx_count, lib_csw.Number.Client):
raise ValueError('csw_idx_count must be an instance of Number.Client')
super().__init__(csw_control=csw_idx)
self._csw_idx = csw_idx
self._csw_idx_count = csw_idx_count
csw_idx.call_on_number(self._on_csw_idx)
csw_idx_count.call_on_number(self._on_csw_idx_count)
# Init UI
slider = self._slider = lib_qt.QXSlider(orientation=Qt.Orientation.Horizontal,
min=0,
max=0,
tick_position=QSlider.TickPosition.NoTicks,
tick_interval=1,
valueChanged=self._on_slider_valueChanged)
spinbox_font = QXFontDB.Digital7_Mono(11, italic=True)
spinbox_index = self._spinbox_index = lib_qt.QXSpinBox( font=spinbox_font, min=0, max=0, step=1, alignment=Qt.AlignmentFlag.AlignRight, button_symbols=QAbstractSpinBox.ButtonSymbols.NoButtons, editingFinished=self._on_spinbox_index_editingFinished)
spinbox_count = self._spinbox_count = lib_qt.QXSpinBox( font=spinbox_font, min=0, max=0, step=1, alignment=Qt.AlignmentFlag.AlignRight, button_symbols=QAbstractSpinBox.ButtonSymbols.NoButtons, readonly=True)
main_l = lib_qt.QXVBoxLayout([
slider,
lib_qt.QXHBoxLayout([lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([spinbox_index, lib_qt.QXLabel(text='/', ), spinbox_count]),
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) )])
])
self.setLayout(main_l)
self.hide()
def _on_csw_idx(self, idx):
#print('_on_csw_idx', idx)
if idx is not None:
with lib_qt.BlockSignals([self._slider, self._spinbox_index]):
self._slider.setValue(idx+1)
self._spinbox_index.setValue(idx+1)
def _on_csw_idx_count(self, idx_count):
#print('_on_csw_idx_count', idx_count)
if idx_count is not None:
with lib_qt.BlockSignals([self._slider, self._spinbox_index, self._spinbox_count]):
self._slider.setMinimum(1)
self._slider.setMaximum(idx_count)
self._spinbox_index.setMinimum(1)
self._spinbox_index.setMaximum(idx_count)
self._spinbox_count.setMaximum(idx_count)
self._spinbox_count.setValue(idx_count)
def _on_slider_valueChanged(self):
self._csw_idx.set_number(self._slider.value()-1)
def _on_spinbox_index_editingFinished(self):
self._csw_idx.set_number(self._spinbox_index.value()-1)

View file

@ -0,0 +1,91 @@
from localization import L
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from resources.fonts import QXFontDB
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QSpinBoxCSWNumber(QCSWControl):
"""
Implements lib_csw.Number control as SpinBox
"""
def __init__(self, csw_number : lib_csw.Number.Client, reflect_state_widgets=None):
if not isinstance(csw_number, lib_csw.Number.Client):
raise ValueError('csw_number must be an instance of Number.Client')
super().__init__(csw_control=csw_number, reflect_state_widgets=reflect_state_widgets)
self._csw_number = csw_number
self._instant_update = False
self._zero_is_auto = False
self._read_only = True
csw_number.call_on_number(self._on_csw_number)
csw_number.call_on_config(self._on_csw_config)
spinbox = self._spinbox = lib_qt.QXDoubleSpinBox( font=QXFontDB.Digital7_Mono(11, italic=True), min=0, max=999999999, step=1, decimals=0, readonly=self._read_only, valueChanged=self._on_spinbox_valueChanged, editingFinished=self._on_spinbox_editingFinished)
btn_auto = self._btn_auto = lib_qt.QXPushButton(text=L('@misc.auto'), hided=True, released=self._on_btn_auto_released, fixed_height=21)
main_l = lib_qt.QXHBoxLayout([spinbox, 1, btn_auto])
self.setLayout(main_l)
self.hide()
def _on_csw_number(self, number):
with lib_qt.BlockSignals(self._spinbox):
self._spinbox.setValue(number)
self._btn_auto_update()
def _btn_auto_update(self):
if self._zero_is_auto and self._spinbox.value() != 0:
self._btn_auto.show()
else:
self._btn_auto.hide()
def _on_csw_config(self, cfg : lib_csw.Number.Config):
if cfg.min is not None:
self._spinbox.setMinimum(cfg.min)
if cfg.max is not None:
self._spinbox.setMaximum(cfg.max)
if cfg.step is not None:
self._spinbox.setSingleStep(cfg.step)
if cfg.decimals is not None:
self._spinbox.setDecimals(cfg.decimals)
self._zero_is_auto = cfg.zero_is_auto
if cfg.zero_is_auto:
self._spinbox.setSpecialValueText(L('@misc.auto'))
else:
self._spinbox.setSpecialValueText('')
self._read_only = cfg.read_only
self._spinbox.setReadOnly(cfg.read_only)
if cfg.read_only:
self._spinbox.setButtonSymbols(QAbstractSpinBox.ButtonSymbols.NoButtons)
else:
self._spinbox.setButtonSymbols(QAbstractSpinBox.ButtonSymbols.UpDownArrows)
self._btn_auto_update()
self._instant_update = cfg.allow_instant_update
def _get_spinbox_value(self):
val = self._spinbox.value()
if self._spinbox.decimals() == 0:
val = int(val)
return val
def _on_btn_auto_released(self):
self._csw_number.set_number(0)
#self._btn_auto_update()
def _on_spinbox_valueChanged(self):
if self._instant_update:
self._csw_number.set_number(self._get_spinbox_value())
def _on_spinbox_editingFinished(self):
if not self._instant_update:
self._csw_number.set_number(self._get_spinbox_value())

View file

@ -0,0 +1,32 @@
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from xlib import qt as lib_qt
from xlib.mp import csw as lib_csw
from .QCSWControl import QCSWControl
class QXPushButtonCSWSignal(QCSWControl):
"""
Implements lib_csw.Signal control as PushButton
"""
def __init__(self, csw_signal : lib_csw.Signal.Client, reflect_state_widgets=None,
image=None,
text=None, button_size=None, button_height=None,):
if not isinstance(csw_signal, lib_csw.Signal.Client):
raise ValueError('csw_signal must be an instance of Signal.Client')
super().__init__(csw_control=csw_signal, reflect_state_widgets=reflect_state_widgets)
self._csw_signal = csw_signal
# Init UI
btn = self._btn = lib_qt.QXPushButton(image=image, text=text, released=self.on_btn_released, minimum_size=button_size, minimum_height=button_height)
main_l = lib_qt.QXHBoxLayout([btn])
self.setLayout(main_l)
self.hide()
def on_btn_released(self):
self._csw_signal.signal()

Binary file not shown.

BIN
build/samples/FaceBall.mp4 Normal file

Binary file not shown.

BIN
build/samples/Obama.mp4 Normal file

Binary file not shown.

BIN
build/samples/Singer.mp4 Normal file

Binary file not shown.

BIN
build/samples/Twitch1.mp4 Normal file

Binary file not shown.

BIN
build/samples/Twitch2.mp4 Normal file

Binary file not shown.

BIN
build/samples/Twitch3.mp4 Normal file

Binary file not shown.

BIN
build/samples/Twitch4.mp4 Normal file

Binary file not shown.

BIN
build/samples/Van Damme.mp4 Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Some files were not shown because too many files have changed in this diff Show more