Commit 78488eb7 authored by dmMaze's avatar dmMaze
Browse files

clean up configs

parent aaba6380
Loading
Loading
Loading
Loading
+52 −91
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ import cv2
import re

from utils.imgproc_utils import union_area, xywh2xyxypoly, rotate_polygons, color_difference
from utils.structures import Tuple, Union, List, Dict, Config, field, nested_dataclass

LANG_LIST = ['eng', 'ja', 'unknown']
LANGCLS2IDX = {'eng': 0, 'ja': 1, 'unknown': 2}
@@ -16,97 +17,57 @@ LANGCLS2IDX = {'eng': 0, 'ja': 1, 'unknown': 2}
CJKPATTERN = re.compile(r'[\uac00-\ud7a3\u3040-\u30ff\u4e00-\u9FFF]')


class TextBlock(object):
    def __init__(self, xyxy: List, 
                 lines: List = None, 
                 language: str = 'unknown',
                 vertical: bool = False, 
                 font_size: float = -1,
                 distance: List = None,
                 angle: int = 0,
                 vec: List = None,
                 norm: float = -1,
                 merged: bool = False,
                 sort_weight: float = -1,
                 text: List = None,
                 translation: str = "",
                 fg_r = 0,
                 fg_g = 0,
                 fg_b = 0,
                 bg_r = 0,
                 bg_g = 0,
                 bg_b = 0,                
                 line_spacing = 1.,
                 letter_spacing = 1.,
                 font_family: str = "",
                 bold: bool = False,
                 underline: bool = False,
                 italic: bool = False,
                 _alignment: int = -1,
                 rich_text: str = "",
                 _bounding_rect: List = None,
                 accumulate_color = True,
                 default_stroke_width = 0.2,
                 stroke_decide_by_colordiff: bool = True,
                 font_weight = 50, 
                 _target_lang: str = "",
                 opacity: float = 1.,
                 shadow_radius: float = 0.,
                 shadow_strength: float = 1.,
                 shadow_color: Tuple = (0, 0, 0),
                 shadow_offset: List = [0, 0],
                 **kwargs) -> None:
        self.xyxy = [int(num) for num in xyxy]                    # boundingbox of textblock
        self.lines = [] if lines is None else lines     # polygons of textlines
        self.vertical = vertical            # orientation of textlines
        self.language = language
        self.font_size = font_size          # font pixel size
        self.distance = None if distance is None else np.array(distance, np.float64)   # distance between textlines and "origin"          
        self.angle = angle                  # rotation angle of textlines

        self.vec = None if vec is None else np.array(vec, np.float64) # primary vector of textblock
        self.norm = norm                    # primary norm of textblock
        self.merged = merged
        self.sort_weight = sort_weight

        self.text = text if text is not None else []
        self.prob = 1

        self.translation = translation

        # note they're accumulative rgb values of textlines
        self.fg_r = fg_r                       
        self.fg_g = fg_g
        self.fg_b = fg_b
        self.bg_r = bg_r
        self.bg_g = bg_g
        self.bg_b = bg_b

        # self.stroke_width = stroke_width
        self.font_family: str = font_family
        self.bold: bool = bold
        self.underline: bool = underline
        self.italic: bool = italic
        self.rich_text = rich_text
        self.line_spacing = line_spacing
        self.letter_spacing = letter_spacing
        self._alignment = _alignment
        self._target_lang = _target_lang

        self._bounding_rect = _bounding_rect
        self.default_stroke_width = default_stroke_width
        self.stroke_decide_by_colordiff = stroke_decide_by_colordiff
        self.font_weight = font_weight
        self.accumulate_color = accumulate_color

        self.opacity = opacity
        self.shadow_radius = shadow_radius
        self.shadow_strength = shadow_strength
        self.shadow_color = shadow_color
        self.shadow_offset = shadow_offset

        self.region_mask: np.ndarray = None
        self.region_inpaint_dict: dict = None
@nested_dataclass
class TextBlock:
    xyxy: List = field(default_factory = lambda: [0, 0, 0, 0])
    lines: List = field(default_factory = lambda: [])
    language: str = 'unknown'
    vertical: bool = False
    font_size: float = -1.
    distance: np.ndarray = None
    angle: int = 0
    vec: List = None
    norm: float = -1
    merged: bool = False
    sort_weight: float = -1
    text: List = field(default_factory = lambda : [])
    translation: str = ""
    fg_r: int = 0
    fg_g: int = 0
    fg_b: int = 0
    bg_r: int = 0
    bg_g: int = 0
    bg_b: int = 0
    line_spacing: float = 1.
    letter_spacing: float = 1.
    font_family: str = ""
    bold: bool = False
    underline: bool = False
    italic: bool = False
    _alignment: int = -1
    rich_text: str = ""
    _bounding_rect: List = None
    accumulate_color: bool = True
    default_stroke_width: float = 0.2
    stroke_decide_by_colordiff: bool = True
    font_weight: int = 50
    _target_lang: str = ""
    opacity: float = 1.
    shadow_radius: float = 0.
    shadow_strength: float = 1.
    shadow_color: Tuple = (0, 0, 0)
    shadow_offset: List = field(default_factory = lambda : [0., 0.])

    region_mask: np.ndarray = None
    region_inpaint_dict: Dict = None

    def __post_init__(self):
        if self.xyxy is not None:
            self.xyxy = [int(num) for num in self.xyxy]
        if self.distance is not None:
            self.distance = np.array(self.distance, np.float32)
        if self.vec is not None:
            self.vec = np.array(self.vec, np.float32)

    def adjust_bbox(self, with_bbox=False, x_range=None, y_range=None):
        lines = self.lines_array().astype(np.int32)
+1 −10
Original line number Diff line number Diff line
@@ -280,15 +280,6 @@ class TransGoogle(BaseTranslator):
    concate_text = True
    params: Dict = {
        'delay': '0.0',
        'url': {
            'type': 'selector',
            'options': [
                # 'https://translate.google.cn/m',
                'https://translate.google.com/m'
            ],
            'select': 'https://translate.google.com/m'
        },
        
    }
    
    def _setup_translator(self):
@@ -320,7 +311,7 @@ class TransGoogle(BaseTranslator):
        self.googletrans._url_params['sl'] = self.lang_map[self.lang_source]
        self.googletrans._target = self.lang_map[self.lang_target]
        self.googletrans._url_params['tl'] = self.lang_map[self.lang_target]
        self.googletrans.__base_url = self.params['url']['select']
        self.googletrans.__base_url = "https://translate.google.com/m"
        translations = [self.googletrans.translate(t) for t in src_list]

        return translations
+8 −0
Original line number Diff line number Diff line
@@ -354,6 +354,14 @@ class SizeControlLabel(QLabel):
            self.cur_pos = new_pos
        return super().mouseMoveEvent(e)
    
# dict(FontFormat)
# local_fformat_change_to_func = {
#     'frgb': None,
# }
# global_fformat_change_to_func = {
#     FontFormat.
# }


class FontFormatPanel(Widget):
    
+93 −229
Original line number Diff line number Diff line
@@ -2,15 +2,13 @@ import cv2, re, json, os
from pathlib import Path
import numpy as np
import os.path as osp
from typing import Tuple, Union, List, Dict
from qtpy.QtGui import QPixmap,  QColor, QImage, QTextDocument, QTextCursor

from . import constants as C
from .constants import DEFAULT_FONT_FAMILY, STYLESHEET_PATH, THEME_PATH
from utils.io_utils import find_all_imgs, NumpyEncoder, imread, imwrite
from utils.io_utils import NumpyEncoder
from utils.structures import Tuple, Union, List, Dict, Config, field, nested_dataclass
from modules.textdetector.textblock import TextBlock


# return bgr tuple
def qrgb2bgr(color: Union[QColor, Tuple, List] = None) -> Tuple[int, int, int]:
    if color is not None:
@@ -82,46 +80,27 @@ class InvalidModuleConfigException(Exception):
class InvalidProgramConfigException(Exception):
    pass


class FontFormat:
    def __init__(self, 
                 family: str = None,
                 size: float = 24,
                 stroke_width: float = 0,
                 frgb=(0, 0, 0),
                 srgb=(0, 0, 0),
                 bold: bool = False,
                 underline: bool = False,
                 italic: bool = False, 
                 alignment: int = 0,
                 vertical: bool = False, 
                 weight: int = 50, 
                 line_spacing: float = 1.2,
                 letter_spacing: float = 1.,
                 opacity: float = 1.,
                 shadow_radius: float = 0.,
                 shadow_strength: float = 1.,
                 shadow_color: Tuple = (0, 0, 0),
                 shadow_offset: List = [0, 0],
                 **kwargs) -> None:
        self.family = family if family is not None else DEFAULT_FONT_FAMILY
        self.size = size
        self.stroke_width = stroke_width
        self.frgb = frgb                  # font color
        self.srgb = srgb                    # stroke color
        self.bold = bold
        self.underline = underline
        self.italic = italic
        self.weight: int = weight
        self.alignment: int = alignment
        self.vertical: bool = vertical
        self.line_spacing = line_spacing
        self.letter_spacing = letter_spacing
        self.opacity = opacity
        self.shadow_radius = shadow_radius
        self.shadow_strength = shadow_strength
        self.shadow_color = shadow_color
        self.shadow_offset = shadow_offset
@nested_dataclass
class FontFormat(Config):

    family: str = C.DEFAULT_FONT_FAMILY
    size: float = 24
    stroke_width: float = 0
    frgb: Tuple = (0, 0, 0)
    srgb: Tuple = (0, 0, 0)
    bold: bool = False
    underline: bool = False
    italic: bool = False
    alignment: int = 0
    vertical: bool = False
    weight: int = 50
    line_spacing: float = 1.2
    letter_spacing: float = 1.
    opacity: float = 1.
    shadow_radius: float = 0.
    shadow_strength: float = 1.
    shadow_color: Tuple = (0, 0, 0)
    shadow_offset: List = field(default_factory=lambda: [0., 0.])

    def from_textblock(self, text_block: TextBlock):
        self.family = text_block.font_family
@@ -148,191 +127,76 @@ class ProjHardSubExtract:
        self.type = 'hardsubextract'
        raise NotImplementedProjException('hardsubextract')


class ModuleConfig:
    def __init__(self, 
                 textdetector: str = 'ctd',
                 ocr = "mit48px_ctc",
                 inpainter: str = 'lama_mpe',
                 translator = "google",
                 enable_ocr = True,
                 enable_translate = True,
                 enable_inpaint = True,
                 textdetector_params = None,
                 ocr_params = None,
                 translator_params = None,
                 inpainter_params = None,
                 translate_source = '日本語',
                 translate_target = '简体中文',
                 check_need_inpaint = True,
                 ) -> None:

        self.textdetector = textdetector
        self.ocr = ocr
        self.inpainter = inpainter
        self.translator = translator
        self.enable_ocr = enable_ocr
        self.enable_translate = enable_translate
        self.enable_inpaint = enable_inpaint
        if textdetector_params is None:
            self.textdetector_params = dict()
        else:
            self.textdetector_params = textdetector_params
        if ocr_params is None:
            self.ocr_params = dict()
        else:
            self.ocr_params = ocr_params
        if translator_params is None:
            self.translator_params = dict()
        else:
            self.translator_params = translator_params
            if 'google' in translator_params:
                if 'url' in translator_params['google'] and \
                    translator_params['google']['url']['select'] == 'https://translate.google.cn/m':
                    translator_params['google']['url']['select'] = 'https://translate.google.com/m'
        if inpainter_params is None:
            self.inpainter_params = dict()
        else:
            self.inpainter_params = inpainter_params
        self.translate_source = translate_source
        self.translate_target = translate_target
        self.check_need_inpaint = check_need_inpaint

    def __getitem__(self, item: str):
        if item == 'textdetector':
            return self.textdetector
        elif item == 'ocr':
            return self.ocr
        elif item == 'translator':
            return self.translator
        elif item == 'inpainter':
            return self.inpainter
        else:
            raise KeyError(item)
@nested_dataclass
class ModuleConfig(Config):
    textdetector: str = 'ctd'
    ocr: str = "mit48px_ctc"
    inpainter: str = 'lama_mpe'
    translator: str = "google"
    enable_ocr: bool = True
    enable_translate: bool = True
    enable_inpaint: bool = True
    textdetector_params: Dict = field(default_factory=lambda: dict())
    ocr_params: Dict = field(default_factory=lambda: dict())
    translator_params: Dict = field(default_factory=lambda: dict())
    inpainter_params: Dict = field(default_factory=lambda: dict())
    translate_source: str = '日本語'
    translate_target: str = '简体中文'
    check_need_inpaint: bool = True

    def get_params(self, module_key: str) -> dict:
        if module_key == 'textdetector':
            return self.textdetector_params
        elif module_key == 'ocr':
            return self.ocr_params
        elif module_key == 'translator':
            return self.translator_params
        elif module_key == 'inpainter':
            return self.inpainter_params


class DrawPanelConfig:
    def __init__(self, 
                 pentool_color: List = None,
                 pentool_width: float = 30.,
                 pentool_shape: int = 0,
                 inpainter_width: float = 30.,
                 inpainter_shape: int = 0,
                 current_tool: int = 0,
                 rectool_auto: bool = False, 
                 rectool_method: int = 0,
                 recttool_dilate_ksize: int = 0,
                 **kwargs) -> None:
        self.pentool_color = pentool_color if pentool_color is not None else [0, 0, 0]
        self.pentool_width = pentool_width
        self.pentool_shape = pentool_shape
        self.inpainter_width = inpainter_width
        self.inpainter_shape = inpainter_shape
        self.current_tool = current_tool
        self.rectool_auto = rectool_auto
        self.rectool_method = rectool_method
        self.recttool_dilate_ksize = recttool_dilate_ksize


class ProgramConfig:
    def __init__(
        self, module: Union[Dict, ModuleConfig] = None,
        drawpanel: Union[Dict, DrawPanelConfig] = None,
        global_fontformat: Union[Dict, FontFormat] = None,
        recent_proj_list: List[str] = list(),
        imgtrans_paintmode: bool = False,
        imgtrans_textedit: bool = True,
        imgtrans_textblock: bool = True,
        mask_transparency: float = 0.,
        original_transparency: float = 0.,
        open_recent_on_startup: bool = True, 
        let_fntsize_flag: int = 0,
        let_fntstroke_flag: int = 0,
        let_fntcolor_flag: int = 0,
        let_fnt_scolor_flag: int = 0,
        let_fnteffect_flag: int = 1,
        let_alignment_flag: int = 0,
        let_autolayout_flag: bool = True,
        let_uppercase_flag: bool = True,
        font_presets: dict = None,
        fsearch_case: bool = False,
        fsearch_whole_word: bool = False,
        fsearch_regex: bool = False,
        fsearch_range: int = 0,
        gsearch_case: bool = False,
        gsearch_whole_word: bool = False,
        gsearch_regex: bool = False,
        gsearch_range: int = 0,
        darkmode: bool = False,
        src_link_flag: str = '',
        textselect_mini_menu: bool = True,
        saladict_shortcut: str = "Alt+S",
        search_url: str = "https://www.google.com/search?q=",
        ocr_sublist: dict = None,
        mt_sublist: dict = None,
        display_lang: str = C.DEFAULT_DISPLAY_LANG,
        **kwargs) -> None:

        if isinstance(module, dict):
            self.module = ModuleConfig(**module)
        elif module is None:
            self.module = ModuleConfig()
        else:
            self.module = module
        if isinstance(drawpanel, dict):
            self.drawpanel = DrawPanelConfig(**drawpanel)
        elif drawpanel is None:
            self.drawpanel = DrawPanelConfig()
        else:
            self.drawpanel = drawpanel
        if isinstance(global_fontformat, dict):
            self.global_fontformat = FontFormat(**global_fontformat)
        elif global_fontformat is None:
            self.global_fontformat = FontFormat()
        else:
            self.global_fontformat = global_fontformat
        self.recent_proj_list = recent_proj_list
        self.imgtrans_paintmode = imgtrans_paintmode
        self.imgtrans_textedit = imgtrans_textedit
        self.imgtrans_textblock = imgtrans_textblock
        self.mask_transparency = mask_transparency
        self.original_transparency = original_transparency
        self.open_recent_on_startup = open_recent_on_startup
        self.let_fntsize_flag = let_fntsize_flag
        self.let_fntstroke_flag = let_fntstroke_flag
        self.let_fntcolor_flag = let_fntcolor_flag
        self.let_fnt_scolor_flag = let_fnt_scolor_flag
        self.let_fnteffect_flag = let_fnteffect_flag
        self.let_alignment_flag = let_alignment_flag
        self.let_autolayout_flag = let_autolayout_flag
        self.let_uppercase_flag = let_uppercase_flag
        self.font_presets = {} if font_presets is None else font_presets
        self.fsearch_case = fsearch_case
        self.fsearch_whole_word = fsearch_whole_word
        self.fsearch_regex = fsearch_regex
        self.fsearch_range = fsearch_range
        self.gsearch_case = gsearch_case
        self.gsearch_whole_word = gsearch_whole_word
        self.gsearch_regex = gsearch_regex
        self.gsearch_range = gsearch_range
        self.darkmode = darkmode
        self.src_link_flag = src_link_flag
        self.textselect_mini_menu = textselect_mini_menu
        self.saladict_shortcut = saladict_shortcut
        self.search_url = search_url
        self.ocr_sublist = [] if ocr_sublist is None else ocr_sublist
        self.mt_sublist = [] if mt_sublist is None else mt_sublist
        self.display_lang = display_lang
        return self[module_key + '_params']

@nested_dataclass
class DrawPanelConfig(Config):
    pentool_color: List = field(default_factory=lambda: [0, 0, 0])
    pentool_width: float = 30.
    pentool_shape: int = 0
    inpainter_width: float = 30.
    inpainter_shape: int = 0
    current_tool: int = 0
    rectool_auto: bool = False
    rectool_method: int = 0
    recttool_dilate_ksize: int = 0

@nested_dataclass
class ProgramConfig(Config):

    module: ModuleConfig = field(default_factory=lambda: ModuleConfig()),
    drawpanel: DrawPanelConfig = field(default_factory=lambda: DrawPanelConfig())
    global_fontformat: FontFormat = field(default_factory=lambda: FontFormat())
    recent_proj_list: List = field(default_factory=lambda: [])
    imgtrans_paintmode: bool = False
    imgtrans_textedit: bool = True
    imgtrans_textblock: bool = True
    mask_transparency: float = 0.
    original_transparency: float = 0.
    open_recent_on_startup: bool = True 
    let_fntsize_flag: int = 0
    let_fntstroke_flag: int = 0
    let_fntcolor_flag: int = 0
    let_fnt_scolor_flag: int = 0
    let_fnteffect_flag: int = 1
    let_alignment_flag: int = 0
    let_autolayout_flag: bool = True
    let_uppercase_flag: bool = True
    font_presets: dict = field(default_factory=lambda: dict())
    fsearch_case: bool = False
    fsearch_whole_word: bool = False
    fsearch_regex: bool = False
    fsearch_range: int = 0
    gsearch_case: bool = False
    gsearch_whole_word: bool = False
    gsearch_regex: bool = False
    gsearch_range: int = 0
    darkmode: bool = False
    src_link_flag: str = ''
    textselect_mini_menu: bool = True
    saladict_shortcut: str = "Alt+S"
    search_url: str = "https://www.google.com/search?q="
    ocr_sublist: dict = field(default_factory=lambda: [])
    mt_sublist: dict = field(default_factory=lambda: [])
    display_lang: str = C.DEFAULT_DISPLAY_LANG

    @staticmethod
    def load(cfg_path: str):
@@ -459,9 +323,9 @@ def parse_stylesheet(theme: str = '', reverse_icon: bool = False) -> str:
    if reverse_icon:
        dark2light = True if theme == 'eva-light' else False
        reverse_icon_color(dark2light)
    with open(STYLESHEET_PATH, "r", encoding='utf-8') as f:
    with open(C.STYLESHEET_PATH, "r", encoding='utf-8') as f:
        stylesheet = f.read()
    with open(THEME_PATH, 'r', encoding='utf8') as f:
    with open(C.THEME_PATH, 'r', encoding='utf8') as f:
        theme_dict: Dict = json.loads(f.read())
    if not theme or theme not in theme_dict:
        tgt_theme: Dict = theme_dict[list(theme_dict.keys())[0]]

utils/structures.py

0 → 100644
+55 −0
Original line number Diff line number Diff line
from typing import Tuple, List, ClassVar, Union, Any, Dict
from dataclasses import dataclass, field, is_dataclass

# decorator to wrap original __init__
# https://www.geeksforgeeks.org/creating-nested-dataclass-objects-in-python/
def nested_dataclass(*args, **dataclass_kwargs):
    '''
    nested dataclass support \n
    also ignore extra arguments 
    '''
    def wrapper(check_class):
          
        # passing class to investigate
        check_class = dataclass(check_class, **dataclass_kwargs)
        o_init = check_class.__init__
          
        def __init__(self, *args, **kwargs):
              
            for name in list(kwargs.keys()):
                if name not in self.__annotations__:
                    # print(f'warning: type object \'{self.__class__.__name__}\' has no attribute {name}, might be loading from an older config')
                    kwargs.pop(name)
                    continue
                value = kwargs[name]
                # getting field type
                ft = check_class.__annotations__.get(name, None)
                  
                if is_dataclass(ft) and isinstance(value, dict):
                    obj = ft(**value)
                    kwargs[name]= obj
                    
            o_init(self, *args, **kwargs)
        check_class.__init__=__init__
          
        return check_class
      
    return wrapper(args[0]) if args else wrapper

@dataclass
class Config:
    
    def update(self, key: str, value):
        assert key in self.__annotations__, f'type object \'{self.__class__.__name__}\' has no attribute {key}'
        self.__setattr__(key, value)

    @classmethod
    def annotations_set(cls):
        return set(list(cls.__annotations__))
    
    def __getitem__(self, key: str):
        assert key in self.__annotations__, f'type object \'{self.__class__.__name__}\' has no attribute {key}'
        return self.__getattribute__(key)
    
    def __setitem__(self, key: str, value):
        self.__setattr__(key, value)
 No newline at end of file