├── .github └── workflows │ ├── autotag.yml │ ├── lint.yml │ └── pypipublish.yml ├── .gitignore ├── LICENSE ├── README.md ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py └── vsrgtools ├── __init__.py ├── _metadata.py ├── aka_expr ├── __init__.py ├── _rg.py └── _rp.py ├── blur.py ├── contra.py ├── enum.py ├── freqs.py ├── limit.py ├── py.typed ├── rgtools.py ├── sharp.py └── util.py /.github/workflows/autotag.yml: -------------------------------------------------------------------------------- 1 | name: Check and create tag 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - vsrgtools/_metadata.py 8 | 9 | jobs: 10 | new_version: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Get version number 17 | run: | 18 | echo "CURR_VER=v$(python -c "import runpy;runpy.run_path('vsrgtools/_metadata.py', None, '__github__')")" >> $GITHUB_ENV 19 | - name: Check if version exists 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | uses: mukunku/tag-exists-action@v1.0.0 23 | id: tagcheck 24 | with: 25 | tag: ${{ env.CURR_VER }} 26 | 27 | - name: Make tag 28 | uses: actions/github-script@v3 29 | if: steps.tagcheck.outputs.exists == 'false' 30 | with: 31 | github-token: ${{ secrets.WORKFLOW_TOKEN }} 32 | script: | 33 | github.git.createRef({ 34 | owner: context.repo.owner, 35 | repo: context.repo.repo, 36 | ref: `refs/tags/${process.env.CURR_VER}`, 37 | sha: context.sha 38 | }) 39 | - name: Fallback 40 | if: steps.tagcheck.outputs.exists == 'true' 41 | run: echo "Nothing to see here, move along citizen" -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Python code with Ruff 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - '**.py' 9 | pull_request: 10 | paths: 11 | - '**.py' 12 | 13 | jobs: 14 | windows: 15 | runs-on: windows-latest 16 | strategy: 17 | matrix: 18 | vs-versions: 19 | - 68 20 | python-version: 21 | - '3.12' 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Install dependencies 32 | run: | 33 | python3 -m pip install --upgrade pip 34 | pip install vapoursynth-portable==${{ matrix.vs-versions }} 35 | pip install -r requirements.txt 36 | pip install -r requirements-dev.txt 37 | 38 | - name: Running ruff 39 | run: ruff check vsrgtools 40 | -------------------------------------------------------------------------------- /.github/workflows/pypipublish.yml: -------------------------------------------------------------------------------- 1 | name: Publish releases to PyPI 2 | on: 3 | push: 4 | tags: 5 | - v[0-9]+** 6 | 7 | jobs: 8 | package_build: 9 | name: Build and push to PyPI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Prep Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.12' 18 | 19 | - name: Install build tools 20 | run: | 21 | python -m pip install build setuptools twine --user 22 | continue-on-error: false 23 | - name: Build wheel 24 | id: wheel 25 | run: | 26 | python -m build --wheel --outdir dist/ 27 | continue-on-error: true 28 | - name: Build source distribution 29 | id: sdist 30 | run: | 31 | python -m build --sdist --outdir dist/ 32 | continue-on-error: true 33 | - name: Check the output 34 | run: | 35 | python -m twine check --strict dist/* 36 | continue-on-error: false 37 | - name: Die on failure 38 | if: steps.wheel.outcome != 'success' && steps.sdist.outcome != 'success' 39 | run: exit 1 40 | - name: Publish to PyPI 41 | uses: pypa/gh-action-pypi-publish@master 42 | with: 43 | user: __token__ 44 | password: ${{ secrets.PYPI_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | pip-wheel-metadata/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | docs/make.bat 73 | docs/Makefile 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # celery beat schedule file 96 | celerybeat-schedule 97 | 98 | # SageMath parsed files 99 | *.sage.py 100 | 101 | # Environments 102 | .env 103 | .venv 104 | env/ 105 | venv/ 106 | ENV/ 107 | env.bak/ 108 | venv.bak/ 109 | 110 | # Spyder project settings 111 | .spyderproject 112 | .spyproject 113 | 114 | # Rope project settings 115 | .ropeproject 116 | 117 | # mkdocs documentation 118 | /site 119 | 120 | # mypy 121 | .mypy_cache/ 122 | .dmypy.json 123 | dmypy.json 124 | 125 | # Pyre type checker 126 | .pyre/ 127 | 128 | # Local test file 129 | *test*.py 130 | 131 | # Unignore tests dir 132 | !tests/ 133 | 134 | # vscode folder 135 | .vscode 136 | 137 | # vsjet folder 138 | .vsjet/ 139 | 140 | # Index files 141 | *.ffindex 142 | *.lwi 143 | 144 | # Video test file 145 | *.mkv 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jaded Encoding Thaumaturgy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vs-rgtools 2 | 3 | > [!CAUTION] 4 | > This package is deprecated! 5 | > Please use https://github.com/Jaded-Encoding-Thaumaturgy/vs-jetpack instead. 6 | 7 | Wrapper for RGVS, RGSF, and various other functions 8 | 9 | #### Dependencies 10 | 11 | - [vstools](https://github.com/Jaded-Encoding-Thaumaturgy/vs-tools/) 12 | - [RGVS](https://github.com/vapoursynth/vs-removegrain) 13 | - [RGSF](https://github.com/IFeelBloated/RGSF) 14 | - [zsmooth](https://github.com/adworacz/zsmooth) 15 | - [Akarin Expr](https://github.com/AkarinVS/vapoursynth-plugin) 16 | 17 | # How to install 18 | **This package is deprecated!** 19 | 20 | Please install https://pypi.org/p/vsjetpack instead. 21 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | packaging>=24.0 2 | pycodestyle>=2.11.1 3 | ruff>=0.6.5 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | VapourSynth>=68 2 | vstools>=3.4.1 3 | vsexprtools>=1.8.1 4 | vskernels>=3.4.2 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | count = True 3 | ignore = W503 4 | max-line-length = 120 5 | exclude = stubs/* 6 | show-source = True 7 | statistics = True 8 | 9 | 10 | [mypy] 11 | ignore_missing_imports = False 12 | 13 | disallow_any_generics = True 14 | 15 | disallow_untyped_defs = True 16 | disallow_incomplete_defs = True 17 | check_untyped_defs = True 18 | disallow_untyped_decorators = True 19 | 20 | no_implicit_optional = True 21 | strict_optional = True 22 | 23 | warn_redundant_casts = True 24 | warn_unused_ignores = True 25 | warn_no_return = True 26 | warn_return_any = True 27 | warn_unreachable = True 28 | 29 | ignore_errors = False 30 | 31 | allow_untyped_globals = False 32 | allow_redefinition = False 33 | implicit_reexport = False 34 | strict_equality = True 35 | 36 | show_error_context = False 37 | show_column_numbers = True 38 | show_error_codes = True 39 | color_output = True 40 | error_summary = True 41 | pretty = True 42 | 43 | 44 | [mypy-cytoolz.*] 45 | ignore_errors = True 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import setuptools 4 | from pathlib import Path 5 | 6 | package_name = 'vsrgtools' 7 | 8 | exec(Path(f'{package_name}/_metadata.py').read_text(), meta := dict[str, str]()) 9 | 10 | readme = Path('README.md').read_text() 11 | requirements = Path('requirements.txt').read_text() 12 | 13 | 14 | setuptools.setup( 15 | name=package_name, 16 | version=meta['__version__'], 17 | author=meta['__author_name__'], 18 | author_email=meta['__author_email__'], 19 | maintainer=meta['__maintainer_name__'], 20 | maintainer_email=meta['__maintainer_email__'], 21 | description=meta['__doc__'], 22 | long_description=readme, 23 | long_description_content_type='text/markdown', 24 | project_urls={ 25 | 'Source Code': 'https://github.com/Jaded-Encoding-Thaumaturgy/vs-rgtools', 26 | 'Contact': 'https://discord.gg/XTpc6Fa9eB', 27 | }, 28 | install_requires=requirements, 29 | python_requires='>=3.12', 30 | packages=[ 31 | package_name, f'{package_name}.aka_expr' 32 | ], 33 | package_data={ 34 | package_name: [ 35 | 'bilateral.cu', 36 | 'py.typed' 37 | ] 38 | }, 39 | classifiers=[ 40 | 'Programming Language :: Python :: 3', 41 | 'License :: OSI Approved :: MIT License', 42 | 'Operating System :: OS Independent', 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /vsrgtools/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F401, F403 2 | 3 | from .blur import * 4 | from .contra import * 5 | from .enum import * 6 | from .freqs import * 7 | from .limit import * 8 | from .rgtools import * 9 | from .sharp import * 10 | -------------------------------------------------------------------------------- /vsrgtools/_metadata.py: -------------------------------------------------------------------------------- 1 | """Wrapper for RemoveGrain, Repair, blurring and various other functions""" 2 | 3 | __version__ = '1.9.1' 4 | 5 | __author_name__, __author_email__ = 'Irrational Encoding Wizardry', 'wizards@encode.moe' 6 | __maintainer_name__, __maintainer_email__ = 'Setsugen no ao', 'setsugen@setsugen.dev' 7 | 8 | __author__ = f'{__author_name__} <{__author_email__}>' 9 | __maintainer__ = __author__ 10 | 11 | if __name__ == '__github__': 12 | print(__version__) 13 | -------------------------------------------------------------------------------- /vsrgtools/aka_expr/__init__.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from typing import Callable 3 | 4 | from ._rg import * # noqa: F401, F403 5 | from ._rg import ( # noqa: F401 6 | aka_removegrain_expr_1, aka_removegrain_expr_2_4, aka_removegrain_expr_5, aka_removegrain_expr_6, 7 | aka_removegrain_expr_7, aka_removegrain_expr_8, aka_removegrain_expr_9, aka_removegrain_expr_10, 8 | aka_removegrain_expr_11_12, aka_removegrain_expr_17, aka_removegrain_expr_18, aka_removegrain_expr_19, 9 | aka_removegrain_expr_20, aka_removegrain_expr_21_22, aka_removegrain_expr_23, aka_removegrain_expr_24, 10 | aka_removegrain_expr_26, aka_removegrain_expr_27, aka_removegrain_expr_28 11 | ) 12 | from ._rp import * # noqa: F401, F403 13 | from ._rp import ( 14 | aka_repair_expr_1_4, aka_repair_expr_5, aka_repair_expr_6, aka_repair_expr_7, aka_repair_expr_8, aka_repair_expr_9, 15 | aka_repair_expr_10, aka_repair_expr_11_14, aka_repair_expr_15, aka_repair_expr_16, aka_repair_expr_17, 16 | aka_repair_expr_18, aka_repair_expr_19, aka_repair_expr_20, aka_repair_expr_21, aka_repair_expr_22, 17 | aka_repair_expr_23, aka_repair_expr_24, aka_repair_expr_26, aka_repair_expr_27, aka_repair_expr_28 18 | ) 19 | 20 | 21 | def _noop_expr() -> str: 22 | return '' 23 | 24 | 25 | removegrain_aka_exprs = list[Callable[[], str]]([ 26 | _noop_expr, aka_removegrain_expr_1, partial(aka_removegrain_expr_2_4, 2), 27 | partial(aka_removegrain_expr_2_4, 3), partial(aka_removegrain_expr_2_4, 4), 28 | aka_removegrain_expr_5, aka_removegrain_expr_6, aka_removegrain_expr_7, aka_removegrain_expr_8, 29 | aka_removegrain_expr_9, aka_removegrain_expr_10, aka_removegrain_expr_11_12, aka_removegrain_expr_11_12, 30 | _noop_expr, _noop_expr, _noop_expr, _noop_expr, 31 | aka_removegrain_expr_17, aka_removegrain_expr_18, aka_removegrain_expr_19, aka_removegrain_expr_20, 32 | aka_removegrain_expr_21_22, aka_removegrain_expr_21_22, _noop_expr, _noop_expr, 33 | _noop_expr, aka_removegrain_expr_26, aka_removegrain_expr_27, aka_removegrain_expr_28 34 | ]) 35 | 36 | repair_aka_exprs = list[Callable[[], str]]([ 37 | _noop_expr, partial(aka_repair_expr_1_4, 1), partial(aka_repair_expr_1_4, 2), 38 | partial(aka_repair_expr_1_4, 3), partial(aka_repair_expr_1_4, 4), 39 | aka_repair_expr_5, aka_repair_expr_6, aka_repair_expr_7, aka_repair_expr_8, 40 | aka_repair_expr_9, aka_repair_expr_10, partial(aka_repair_expr_11_14, 1), partial(aka_repair_expr_11_14, 2), 41 | partial(aka_repair_expr_11_14, 3), partial(aka_repair_expr_11_14, 4), aka_repair_expr_15, aka_repair_expr_16, 42 | aka_repair_expr_17, aka_repair_expr_18, aka_repair_expr_19, aka_repair_expr_20, 43 | aka_repair_expr_21, aka_repair_expr_22, aka_repair_expr_23, aka_repair_expr_24, 44 | _noop_expr, aka_repair_expr_26, aka_repair_expr_27, aka_repair_expr_28 45 | ]) 46 | -------------------------------------------------------------------------------- /vsrgtools/aka_expr/_rg.py: -------------------------------------------------------------------------------- 1 | A1 = 'x[-1,-1]' 2 | A2 = 'x[0,-1]' 3 | A3 = 'x[1,-1]' 4 | A4 = 'x[-1,0]' 5 | A5 = 'x[1,0]' 6 | A6 = 'x[-1,1]' 7 | A7 = 'x[0,1]' 8 | A8 = 'x[1,1]' 9 | c = 'x' 10 | PIXELS = ' '.join([A1, A2, A3, A4, A5, A6, A7, A8]) 11 | 12 | 13 | def aka_removegrain_expr_1() -> str: 14 | return f'x {PIXELS} min min min min min min min {PIXELS} max max max max max max max clamp' 15 | 16 | 17 | def aka_removegrain_expr_2_4(m: int) -> str: 18 | # print(f'dup{9 - m} dup{m - 1}') 19 | return f'{PIXELS} sort8 dup{8 - m} max_val! dup{m - 1} min_val! drop8 x min_val@ max_val@ clamp' 20 | 21 | 22 | def aka_removegrain_expr_5() -> str: 23 | return ( 24 | f'x {A1} {A8} min {A1} {A8} max clamp clamp1! ' 25 | f'x {A2} {A7} min {A2} {A7} max clamp clamp2! ' 26 | f'x {A3} {A6} min {A3} {A6} max clamp clamp3! ' 27 | f'x {A4} {A5} min {A4} {A5} max clamp clamp4! ' 28 | 'x clamp1@ - abs c1! ' 29 | 'x clamp2@ - abs c2! ' 30 | 'x clamp3@ - abs c3! ' 31 | 'x clamp4@ - abs c4! ' 32 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 33 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 34 | ) 35 | 36 | 37 | def aka_removegrain_expr_6() -> str: 38 | return ( 39 | f'{A1} {A8} min mil1! ' 40 | f'{A1} {A8} max mal1! ' 41 | f'{A2} {A7} min mil2! ' 42 | f'{A2} {A7} max mal2! ' 43 | f'{A3} {A6} min mil3! ' 44 | f'{A3} {A6} max mal3! ' 45 | f'{A4} {A5} min mil4! ' 46 | f'{A4} {A5} max mal4! ' 47 | 'mal1@ mil1@ - d1! ' 48 | 'mal2@ mil2@ - d2! ' 49 | 'mal3@ mil3@ - d3! ' 50 | 'mal4@ mil4@ - d4! ' 51 | 'x mil1@ mal1@ clamp clamp1! ' 52 | 'x mil2@ mal2@ clamp clamp2! ' 53 | 'x mil3@ mal3@ clamp clamp3! ' 54 | 'x mil4@ mal4@ clamp clamp4! ' 55 | 'x clamp1@ - abs 2 * d1@ + c1! ' 56 | 'x clamp2@ - abs 2 * d2@ + c2! ' 57 | 'x clamp3@ - abs 2 * d3@ + c3! ' 58 | 'x clamp4@ - abs 2 * d4@ + c4! ' 59 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 60 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 61 | ) 62 | 63 | 64 | def aka_removegrain_expr_7() -> str: 65 | return ( 66 | f'{A1} {A8} min mil1! ' 67 | f'{A1} {A8} max mal1! ' 68 | f'{A2} {A7} min mil2! ' 69 | f'{A2} {A7} max mal2! ' 70 | f'{A3} {A6} min mil3! ' 71 | f'{A3} {A6} max mal3! ' 72 | f'{A4} {A5} min mil4! ' 73 | f'{A4} {A5} max mal4! ' 74 | 'mal1@ mil1@ - d1! ' 75 | 'mal2@ mil2@ - d2! ' 76 | 'mal3@ mil3@ - d3! ' 77 | 'mal4@ mil4@ - d4! ' 78 | 'x mil1@ mal1@ clamp clamp1! ' 79 | 'x mil2@ mal2@ clamp clamp2! ' 80 | 'x mil3@ mal3@ clamp clamp3! ' 81 | 'x mil4@ mal4@ clamp clamp4! ' 82 | # Only change is removing the "* 2" 83 | 'x clamp1@ - abs d1@ + c1! ' 84 | 'x clamp2@ - abs d2@ + c2! ' 85 | 'x clamp3@ - abs d3@ + c3! ' 86 | 'x clamp4@ - abs d4@ + c4! ' 87 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 88 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 89 | ) 90 | 91 | 92 | def aka_removegrain_expr_8() -> str: 93 | return ( 94 | f'{A1} {A8} min mil1! ' 95 | f'{A1} {A8} max mal1! ' 96 | f'{A2} {A7} min mil2! ' 97 | f'{A2} {A7} max mal2! ' 98 | f'{A3} {A6} min mil3! ' 99 | f'{A3} {A6} max mal3! ' 100 | f'{A4} {A5} min mil4! ' 101 | f'{A4} {A5} max mal4! ' 102 | 'mal1@ mil1@ - d1! ' 103 | 'mal2@ mil2@ - d2! ' 104 | 'mal3@ mil3@ - d3! ' 105 | 'mal4@ mil4@ - d4! ' 106 | 'x mil1@ mal1@ clamp clamp1! ' 107 | 'x mil2@ mal2@ clamp clamp2! ' 108 | 'x mil3@ mal3@ clamp clamp3! ' 109 | 'x mil4@ mal4@ clamp clamp4! ' 110 | 'x clamp1@ - abs d1@ 2 * + c1! ' 111 | 'x clamp2@ - abs d2@ 2 * + c2! ' 112 | 'x clamp3@ - abs d3@ 2 * + c3! ' 113 | 'x clamp4@ - abs d4@ 2 * + c4! ' 114 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 115 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 116 | ) 117 | 118 | 119 | def aka_removegrain_expr_9() -> str: 120 | return ( 121 | f'{A1} {A8} min mil1! ' 122 | f'{A1} {A8} max mal1! ' 123 | f'{A2} {A7} min mil2! ' 124 | f'{A2} {A7} max mal2! ' 125 | f'{A3} {A6} min mil3! ' 126 | f'{A3} {A6} max mal3! ' 127 | f'{A4} {A5} min mil4! ' 128 | f'{A4} {A5} max mal4! ' 129 | 'mal1@ mil1@ - d1! ' 130 | 'mal2@ mil2@ - d2! ' 131 | 'mal3@ mil3@ - d3! ' 132 | 'mal4@ mil4@ - d4! ' 133 | 'd1@ d2@ d3@ d4@ min min min mindiff! ' 134 | 'mindiff@ d4@ = x mil4@ mal4@ clamp mindiff@ d2@ = x mil2@ mal2@ clamp ' 135 | 'mindiff@ d3@ = x mil3@ mal3@ clamp x mil1@ mal1@ clamp ? ? ?' 136 | ) 137 | 138 | 139 | def aka_removegrain_expr_10() -> str: 140 | return ( 141 | f'x {A1} - abs d1! ' 142 | f'x {A2} - abs d2! ' 143 | f'x {A3} - abs d3! ' 144 | f'x {A4} - abs d4! ' 145 | f'x {A5} - abs d5! ' 146 | f'x {A6} - abs d6! ' 147 | f'x {A7} - abs d7! ' 148 | f'x {A8} - abs d8! ' 149 | 'd1@ d2@ d3@ d4@ d5@ d6@ d7@ d8@ min min min min min min min mindiff! ' 150 | f'mindiff@ d7@ = {A7} mindiff@ d8@ = {A8} mindiff@ d6@ = {A6} mindiff@ d2@ = {A2} ' 151 | f'mindiff@ d3@ = {A3} mindiff@ d1@ = {A1} mindiff@ d5@ = {A5} {A4} ? ? ? ? ? ? ?' 152 | ) 153 | 154 | 155 | def aka_removegrain_expr_11_12() -> str: 156 | return f'x 4 * {A2} {A4} {A5} {A7} + + + 2 * + {A1} {A3} {A6} {A8} + + + + 16 /' 157 | 158 | 159 | def aka_removegrain_expr_17() -> str: 160 | return ( 161 | f'{A1} {A8} min mil1! ' 162 | f'{A1} {A8} max mal1! ' 163 | f'{A2} {A7} min mil2! ' 164 | f'{A2} {A7} max mal2! ' 165 | f'{A3} {A6} min mil3! ' 166 | f'{A3} {A6} max mal3! ' 167 | f'{A4} {A5} min mil4! ' 168 | f'{A4} {A5} max mal4! ' 169 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 170 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 171 | 'x maxmil@ minmal@ min maxmil@ minmal@ max clamp' 172 | ) 173 | 174 | 175 | def aka_removegrain_expr_18() -> str: 176 | return ( 177 | f'x {A1} - abs x {A8} - abs max d1! ' 178 | f'x {A2} - abs x {A7} - abs max d2! ' 179 | f'x {A3} - abs x {A6} - abs max d3! ' 180 | f'x {A4} - abs x {A5} - abs max d4! ' 181 | 'd1@ d2@ d3@ d4@ min min min mindiff! ' 182 | f'mindiff@ d4@ = x {A4} {A5} min {A4} {A5} max clamp ' 183 | f'mindiff@ d2@ = x {A2} {A7} min {A2} {A7} max clamp ' 184 | f'mindiff@ d3@ = x {A3} {A6} min {A3} {A6} max clamp ' 185 | f'x {A1} {A8} min {A1} {A8} max clamp ? ? ?' 186 | ) 187 | 188 | 189 | def aka_removegrain_expr_19() -> str: 190 | return f'{A1} {A2} {A3} {A4} {A5} {A6} {A7} {A8} + + + + + + + 8.0 /' 191 | 192 | 193 | def aka_removegrain_expr_20() -> str: 194 | return f'x {A1} {A2} {A3} {A4} {A5} {A6} {A7} {A8} + + + + + + + + 9.0 /' 195 | 196 | 197 | def aka_removegrain_expr_21_22() -> str: 198 | return ( 199 | f'{A1} {A8} + 2 / av1! ' 200 | f'{A2} {A7} + 2 / av2! ' 201 | f'{A3} {A6} + 2 / av3! ' 202 | f'{A4} {A5} + 2 / av4! ' 203 | 'x av1@ av2@ av3@ av4@ min min min av1@ av2@ av3@ av4@ max max max clamp' 204 | ) 205 | 206 | 207 | def aka_removegrain_expr_23(peak_min: float) -> str: 208 | minmax = f'min max max max {peak_min} max' 209 | u = f'x mal1@ - linediff1@ min x mal2@ - linediff2@ min x mal3@ - linediff3@ min x mal4@ - linediff4@ {minmax}' 210 | d = f'mil1@ x - linediff1@ min mil2@ x - linediff2@ min mil3@ x - linediff3@ min mil4@ x - linediff4@ {minmax}' 211 | return ( 212 | f'{A1} {A8} min mil1! ' 213 | f'{A1} {A8} max mal1! ' 214 | f'{A2} {A7} min mil2! ' 215 | f'{A2} {A7} max mal2! ' 216 | f'{A3} {A6} min mil3! ' 217 | f'{A3} {A6} max mal3! ' 218 | f'{A4} {A5} min mil4! ' 219 | f'{A4} {A5} max mal4! ' 220 | 'mal1@ mil1@ - linediff1! ' 221 | 'mal2@ mil2@ - linediff2! ' 222 | 'mal3@ mil3@ - linediff3! ' 223 | 'mal4@ mil4@ - linediff4! ' 224 | f'x {u} - {d} +' 225 | ) 226 | 227 | 228 | def aka_removegrain_expr_24(peak_min: float) -> str: 229 | linediff_minmax = ( 230 | 'linediff1@ t1@ - t1@ min linediff2@ t2@ - t2@ min ' 231 | 'linediff3@ t3@ - t3@ min linediff4@ t4@ - t4@ min ' 232 | f'max max max {peak_min} max' 233 | ) 234 | 235 | return ( 236 | f'{A1} {A8} min mil1! ' 237 | f'{A1} {A8} max mal1! ' 238 | f'{A2} {A7} min mil2! ' 239 | f'{A2} {A7} max mal2! ' 240 | f'{A3} {A6} min mil3! ' 241 | f'{A3} {A6} max mal3! ' 242 | f'{A4} {A5} min mil4! ' 243 | f'{A4} {A5} max mal4! ' 244 | 'mal1@ mil1@ - linediff1! ' 245 | 'mal2@ mil2@ - linediff2! ' 246 | 'mal3@ mil3@ - linediff3! ' 247 | 'mal4@ mil4@ - linediff4! ' 248 | 'x mal1@ - t1! ' 249 | 'x mal2@ - t2! ' 250 | 'x mal3@ - t3! ' 251 | 'x mal4@ - t4! ' 252 | f'{linediff_minmax} u! ' 253 | 'mil1@ x - t1! ' 254 | 'mil2@ x - t2! ' 255 | 'mil3@ x - t3! ' 256 | 'mil4@ x - t4! ' 257 | f'{linediff_minmax} d! ' 258 | f'x u@ - d@ +' 259 | ) 260 | 261 | 262 | def aka_removegrain_expr_25() -> str: 263 | raise NotImplementedError 264 | 265 | 266 | def aka_removegrain_expr_26() -> str: 267 | return ( 268 | f'{A1} {A2} min mil1! ' 269 | f'{A1} {A2} max mal1! ' 270 | f'{A2} {A3} min mil2! ' 271 | f'{A2} {A3} max mal2! ' 272 | f'{A3} {A5} min mil3! ' 273 | f'{A3} {A5} max mal3! ' 274 | f'{A5} {A8} min mil4! ' 275 | f'{A5} {A8} max mal4! ' 276 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 277 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 278 | f'{A7} {A8} min mil1! ' 279 | f'{A7} {A8} max mal1! ' 280 | f'{A6} {A7} min mil2! ' 281 | f'{A6} {A7} max mal2! ' 282 | f'{A4} {A6} min mil3! ' 283 | f'{A4} {A6} max mal3! ' 284 | f'{A1} {A4} min mil4! ' 285 | f'{A1} {A4} max mal4! ' 286 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 287 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 288 | 'x maxmil@ minmal@ min maxmil@ minmal@ max clamp' 289 | ) 290 | 291 | 292 | def aka_removegrain_expr_27() -> str: 293 | return ( 294 | f'{A1} {A8} min mil1! ' 295 | f'{A1} {A8} max mal1! ' 296 | f'{A1} {A2} min mil2! ' 297 | f'{A1} {A2} max mal2! ' 298 | f'{A7} {A8} min mil3! ' 299 | f'{A7} {A8} max mal3! ' 300 | f'{A2} {A7} min mil4! ' 301 | f'{A2} {A7} max mal4! ' 302 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 303 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 304 | f'{A2} {A3} min mil1! ' 305 | f'{A2} {A3} max mal1! ' 306 | f'{A6} {A7} min mil2! ' 307 | f'{A6} {A7} max mal2! ' 308 | f'{A3} {A6} min mil3! ' 309 | f'{A3} {A6} max mal3! ' 310 | f'{A3} {A5} min mil4! ' 311 | f'{A3} {A5} max mal4! ' 312 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 313 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 314 | f'{A4} {A6} min mil1! ' 315 | f'{A4} {A6} max mal1! ' 316 | f'{A4} {A5} min mil2! ' 317 | f'{A4} {A5} max mal2! ' 318 | f'{A5} {A8} min mil3! ' 319 | f'{A5} {A8} max mal3! ' 320 | f'{A1} {A4} min mil4! ' 321 | f'{A1} {A4} max mal4! ' 322 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 323 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 324 | 'x maxmil@ minmal@ min maxmil@ minmal@ max clamp' 325 | ) 326 | 327 | 328 | def aka_removegrain_expr_28() -> str: 329 | return ( 330 | f'{A1} {A2} min mil1! ' 331 | f'{A1} {A2} max mal1! ' 332 | f'{A2} {A3} min mil2! ' 333 | f'{A2} {A3} max mal2! ' 334 | f'{A3} {A5} min mil3! ' 335 | f'{A3} {A5} max mal3! ' 336 | f'{A5} {A8} min mil4! ' 337 | f'{A5} {A8} max mal4! ' 338 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 339 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 340 | f'{A7} {A8} min mil1! ' 341 | f'{A7} {A8} max mal1! ' 342 | f'{A6} {A7} min mil2! ' 343 | f'{A6} {A7} max mal2! ' 344 | f'{A4} {A6} min mil3! ' 345 | f'{A4} {A6} max mal3! ' 346 | f'{A1} {A5} min mil4! ' 347 | f'{A1} {A5} max mal4! ' 348 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 349 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 350 | f'{A1} {A8} min mil1! ' 351 | f'{A1} {A8} max mal1! ' 352 | f'{A3} {A6} min mil2! ' 353 | f'{A3} {A6} max mal2! ' 354 | f'{A2} {A7} min mil3! ' 355 | f'{A2} {A7} max mal3! ' 356 | f'{A4} {A5} min mil4! ' 357 | f'{A4} {A5} max mal4! ' 358 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 359 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 360 | 'x maxmil@ minmal@ min maxmil@ minmal@ max clamp' 361 | ) 362 | -------------------------------------------------------------------------------- /vsrgtools/aka_expr/_rp.py: -------------------------------------------------------------------------------- 1 | A1 = 'y[-1,-1]' 2 | A2 = 'y[0,-1]' 3 | A3 = 'y[1,-1]' 4 | A4 = 'y[-1,0]' 5 | A5 = 'y[1,0]' 6 | A6 = 'y[-1,1]' 7 | A7 = 'y[0,1]' 8 | A8 = 'y[1,1]' 9 | c = 'y' 10 | val = 'x' 11 | PIXELS = ' '.join([A1, A2, A3, A4, A5, A6, A7, A8]) 12 | 13 | 14 | def aka_repair_expr_1_4(m: int) -> str: 15 | return f'{PIXELS} y sort9 dup{9 - m} max_val! dup{m - 1} min_val! drop9 x min_val@ max_val@ clamp' 16 | 17 | 18 | def aka_repair_expr_5() -> str: 19 | return ( 20 | f'x y {A1} {A8} min min y {A1} {A8} max max clamp clamp1! ' 21 | f'x y {A2} {A7} min min y {A2} {A7} max max clamp clamp2! ' 22 | f'x y {A3} {A6} min min y {A3} {A6} max max clamp clamp3! ' 23 | f'x y {A4} {A5} min min y {A4} {A5} max max clamp clamp4! ' 24 | 'x clamp1@ - abs c1! ' 25 | 'x clamp2@ - abs c2! ' 26 | 'x clamp3@ - abs c3! ' 27 | 'x clamp4@ - abs c4! ' 28 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 29 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 30 | ) 31 | 32 | 33 | def aka_repair_expr_6() -> str: 34 | return ( 35 | f'y {A1} {A8} min min mil1! ' 36 | f'y {A1} {A8} max max mal1! ' 37 | f'y {A2} {A7} min min mil2! ' 38 | f'y {A2} {A7} max max mal2! ' 39 | f'y {A3} {A6} min min mil3! ' 40 | f'y {A3} {A6} max max mal3! ' 41 | f'y {A4} {A5} min min mil4! ' 42 | f'y {A4} {A5} max max mal4! ' 43 | 'mal1@ mil1@ - d1! ' 44 | 'mal2@ mil2@ - d2! ' 45 | 'mal3@ mil3@ - d3! ' 46 | 'mal4@ mil4@ - d4! ' 47 | 'x mil1@ mal1@ clamp clamp1! ' 48 | 'x mil2@ mal2@ clamp clamp2! ' 49 | 'x mil3@ mal3@ clamp clamp3! ' 50 | 'x mil4@ mal4@ clamp clamp4! ' 51 | 'x clamp1@ - abs 2 * d1@ + c1! ' 52 | 'x clamp2@ - abs 2 * d2@ + c2! ' 53 | 'x clamp3@ - abs 2 * d3@ + c3! ' 54 | 'x clamp4@ - abs 2 * d4@ + c4! ' 55 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 56 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 57 | ) 58 | 59 | 60 | def aka_repair_expr_7() -> str: 61 | return ( 62 | f'y {A1} {A8} min min mil1! ' 63 | f'y {A1} {A8} max max mal1! ' 64 | f'y {A2} {A7} min min mil2! ' 65 | f'y {A2} {A7} max max mal2! ' 66 | f'y {A3} {A6} min min mil3! ' 67 | f'y {A3} {A6} max max mal3! ' 68 | f'y {A4} {A5} min min mil4! ' 69 | f'y {A4} {A5} max max mal4! ' 70 | 'mal1@ mil1@ - d1! ' 71 | 'mal2@ mil2@ - d2! ' 72 | 'mal3@ mil3@ - d3! ' 73 | 'mal4@ mil4@ - d4! ' 74 | 'x mil1@ mal1@ clamp clamp1! ' 75 | 'x mil2@ mal2@ clamp clamp2! ' 76 | 'x mil3@ mal3@ clamp clamp3! ' 77 | 'x mil4@ mal4@ clamp clamp4! ' 78 | # Only change is removing the "* 2" 79 | 'x clamp1@ - abs d1@ + c1! ' 80 | 'x clamp2@ - abs d2@ + c2! ' 81 | 'x clamp3@ - abs d3@ + c3! ' 82 | 'x clamp4@ - abs d4@ + c4! ' 83 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 84 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 85 | ) 86 | 87 | 88 | def aka_repair_expr_8() -> str: 89 | return ( 90 | f'y {A1} {A8} min min mil1! ' 91 | f'y {A1} {A8} max max mal1! ' 92 | f'y {A2} {A7} min min mil2! ' 93 | f'y {A2} {A7} max max mal2! ' 94 | f'y {A3} {A6} min min mil3! ' 95 | f'y {A3} {A6} max max mal3! ' 96 | f'y {A4} {A5} min min mil4! ' 97 | f'y {A4} {A5} max max mal4! ' 98 | 'mal1@ mil1@ - d1! ' 99 | 'mal2@ mil2@ - d2! ' 100 | 'mal3@ mil3@ - d3! ' 101 | 'mal4@ mil4@ - d4! ' 102 | 'x mil1@ mal1@ clamp clamp1! ' 103 | 'x mil2@ mal2@ clamp clamp2! ' 104 | 'x mil3@ mal3@ clamp clamp3! ' 105 | 'x mil4@ mal4@ clamp clamp4! ' 106 | 'x clamp1@ - abs d1@ 2 * + c1! ' 107 | 'x clamp2@ - abs d2@ 2 * + c2! ' 108 | 'x clamp3@ - abs d3@ 2 * + c3! ' 109 | 'x clamp4@ - abs d4@ 2 * + c4! ' 110 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 111 | 'mindiff@ c4@ = clamp4@ mindiff@ c2@ = clamp2@ mindiff@ c3@ = clamp3@ clamp1@ ? ? ?' 112 | ) 113 | 114 | 115 | def aka_repair_expr_9() -> str: 116 | return ( 117 | f'y {A1} {A8} min min mil1! ' 118 | f'y {A1} {A8} max max mal1! ' 119 | f'y {A2} {A7} min min mil2! ' 120 | f'y {A2} {A7} max max mal2! ' 121 | f'y {A3} {A6} min min mil3! ' 122 | f'y {A3} {A6} max max mal3! ' 123 | f'y {A4} {A5} min min mil4! ' 124 | f'y {A4} {A5} max max mal4! ' 125 | 'mal1@ mil1@ - d1! ' 126 | 'mal2@ mil2@ - d2! ' 127 | 'mal3@ mil3@ - d3! ' 128 | 'mal4@ mil4@ - d4! ' 129 | 'd1@ d2@ d3@ d4@ min min min mindiff! ' 130 | 'mindiff@ d4@ = x mil4@ mal4@ clamp mindiff@ d2@ = x mil2@ mal2@ clamp ' 131 | 'mindiff@ d3@ = x mil3@ mal3@ clamp x mil1@ mal1@ clamp ? ? ?' 132 | ) 133 | 134 | 135 | def aka_repair_expr_10() -> str: 136 | return ( 137 | f'x {A1} - abs d1! ' 138 | f'x {A2} - abs d2! ' 139 | f'x {A3} - abs d3! ' 140 | f'x {A4} - abs d4! ' 141 | f'x {A5} - abs d5! ' 142 | f'x {A6} - abs d6! ' 143 | f'x {A7} - abs d7! ' 144 | f'x {A8} - abs d8! ' 145 | f'x y - abs dc! ' 146 | 'd1@ d2@ d3@ d4@ d5@ d6@ d7@ d8@ dc@ min min min min min min min min mindiff! ' 147 | f'mindiff@ d7@ = {A7} mindiff@ d8@ = {A8} mindiff@ d6@ = {A6} mindiff@ d2@ = {A2} ' 148 | f'mindiff@ d3@ = {A3} mindiff@ d1@ = {A1} mindiff@ d5@ = {A5} mindiff@ dc@ = y {A4} ? ? ? ? ? ? ? ?' 149 | ) 150 | 151 | 152 | def aka_repair_expr_11_14(m: int) -> str: 153 | return f'{PIXELS} sort8 dup{8 - m} max_val! dup{m - 1} min_val! drop8 x y min_val@ min y max_val@ max clamp' 154 | 155 | 156 | def aka_repair_expr_15() -> str: 157 | return ( 158 | f'{A1} {A8} min mil1! ' 159 | f'{A1} {A8} max mal1! ' 160 | f'{A2} {A7} min mil2! ' 161 | f'{A2} {A7} max mal2! ' 162 | f'{A3} {A6} min mil3! ' 163 | f'{A3} {A6} max mal3! ' 164 | f'{A4} {A5} min mil4! ' 165 | f'{A4} {A5} max mal4! ' 166 | 'y mil1@ mal1@ clamp clamp1! ' 167 | 'y mil2@ mal2@ clamp clamp2! ' 168 | 'y mil3@ mal3@ clamp clamp3! ' 169 | 'y mil4@ mal4@ clamp clamp4! ' 170 | 'y clamp1@ - abs c1! ' 171 | 'y clamp2@ - abs c2! ' 172 | 'y clamp3@ - abs c3! ' 173 | 'y clamp4@ - abs c4! ' 174 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 175 | 'mindiff@ c4@ = x y mil4@ min y mal4@ max clamp ' 176 | 'mindiff@ c2@ = x y mil2@ min y mal2@ max clamp ' 177 | 'mindiff@ c3@ = x y mil3@ min y mal3@ max clamp ' 178 | 'x y mil1@ min y mal1@ max clamp ? ? ?' 179 | ) 180 | 181 | 182 | def aka_repair_expr_16() -> str: 183 | return ( 184 | f'{A1} {A8} min mil1! ' 185 | f'{A1} {A8} max mal1! ' 186 | f'{A2} {A7} min mil2! ' 187 | f'{A2} {A7} max mal2! ' 188 | f'{A3} {A6} min mil3! ' 189 | f'{A3} {A6} max mal3! ' 190 | f'{A4} {A5} min mil4! ' 191 | f'{A4} {A5} max mal4! ' 192 | 'mal1@ mil1@ - d1! ' 193 | 'mal2@ mil2@ - d2! ' 194 | 'mal3@ mil3@ - d3! ' 195 | 'mal4@ mil4@ - d4! ' 196 | 'y y mil1@ mal1@ clamp - abs 2 * d1@ + c1! ' 197 | 'y y mil2@ mal2@ clamp - abs 2 * d2@ + c2! ' 198 | 'y y mil3@ mal3@ clamp - abs 2 * d3@ + c3! ' 199 | 'y y mil4@ mal4@ clamp - abs 2 * d4@ + c4! ' 200 | 'c1@ c2@ c3@ c4@ min min min mindiff! ' 201 | 'mindiff@ c4@ = x y mil4@ min y mal4@ max clamp ' 202 | 'mindiff@ c2@ = x y mil2@ min y mal2@ max clamp ' 203 | 'mindiff@ c3@ = x y mil3@ min y mal3@ max clamp ' 204 | 'x y mil1@ min y mal1@ max clamp ? ? ?' 205 | ) 206 | 207 | 208 | def aka_repair_expr_17() -> str: 209 | return ( 210 | f'{A1} {A8} min mil1! ' 211 | f'{A1} {A8} max mal1! ' 212 | f'{A2} {A7} min mil2! ' 213 | f'{A2} {A7} max mal2! ' 214 | f'{A3} {A6} min mil3! ' 215 | f'{A3} {A6} max mal3! ' 216 | f'{A4} {A5} min mil4! ' 217 | f'{A4} {A5} max mal4! ' 218 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 219 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 220 | 'x y maxmil@ minmal@ min min y maxmil@ minmal@ max max clamp' 221 | ) 222 | 223 | 224 | def aka_repair_expr_18() -> str: 225 | return ( 226 | f'y {A1} - abs y {A8} - abs max d1! ' 227 | f'y {A2} - abs y {A7} - abs max d2! ' 228 | f'y {A3} - abs y {A6} - abs max d3! ' 229 | f'y {A4} - abs y {A5} - abs max d4! ' 230 | 'd1@ d2@ d3@ d4@ min min min mindiff! ' 231 | f'mindiff@ d4@ = x {A4} {A5} min y min {A4} {A5} max y max clamp ' 232 | f'mindiff@ d2@ = x {A2} {A7} min y min {A2} {A7} max y max clamp ' 233 | f'mindiff@ d3@ = x {A3} {A6} min y min {A3} {A6} max y max clamp ' 234 | f'x {A1} {A8} min y min {A1} {A8} max y max clamp ? ? ?' 235 | ) 236 | 237 | 238 | def aka_repair_expr_19() -> str: 239 | return ( 240 | f'y {A1} - abs y {A2} - abs y {A3} - abs y {A4} - abs y {A5} - abs y {A6} - abs y {A7} - abs y {A8} - abs ' 241 | 'min min min min min min min mindiff! ' 242 | 'x y mindiff@ - y mindiff@ + clamp' 243 | ) 244 | 245 | 246 | def aka_repair_expr_20() -> str: 247 | return ( 248 | f'y {A1} - abs d1! ' 249 | f'y {A2} - abs d2! ' 250 | f'y {A3} - abs d3! ' 251 | f'y {A4} - abs d4! ' 252 | f'y {A5} - abs d5! ' 253 | f'y {A6} - abs d6! ' 254 | f'y {A7} - abs d7! ' 255 | f'y {A8} - abs d8! ' 256 | 'd1@ d2@ min mindiff! ' 257 | 'd1@ d2@ max maxdiff! ' 258 | 'maxdiff@ mindiff@ d3@ clamp maxdiff! ' 259 | 'mindiff@ d3@ min mindiff! ' 260 | 'maxdiff@ mindiff@ d4@ clamp maxdiff! ' 261 | 'mindiff@ d4@ min mindiff! ' 262 | 'maxdiff@ mindiff@ d5@ clamp maxdiff! ' 263 | 'mindiff@ d5@ min mindiff! ' 264 | 'maxdiff@ mindiff@ d6@ clamp maxdiff! ' 265 | 'mindiff@ d6@ min mindiff! ' 266 | 'maxdiff@ mindiff@ d7@ clamp maxdiff! ' 267 | 'mindiff@ d7@ min mindiff! ' 268 | 'maxdiff@ mindiff@ d8@ clamp maxdiff! ' 269 | 'x y maxdiff@ - y maxdiff@ + clamp' 270 | ) 271 | 272 | 273 | def aka_repair_expr_21() -> str: 274 | return ( 275 | f'{A1} {A8} max y - y {A1} {A8} min - max {A2} {A7} max y - y {A2} {A7} min - max ' 276 | f'{A3} {A6} max y - y {A3} {A6} min - max {A4} {A5} max y - y {A4} {A5} min - max min min min u! ' 277 | 'x y u@ - y u@ + clamp' 278 | ) 279 | 280 | 281 | def aka_repair_expr_22() -> str: 282 | return ( 283 | f'x {A1} - abs x {A2} - abs x {A3} - abs x {A4} - abs x {A5} - abs x {A6} - abs x {A7} - abs x {A8} - abs ' 284 | 'min min min min min min min mindiff! ' 285 | 'y x mindiff@ - x mindiff@ + clamp' 286 | ) 287 | 288 | 289 | def aka_repair_expr_23() -> str: 290 | return ( 291 | f'x {A1} - abs d1! ' 292 | f'x {A2} - abs d2! ' 293 | f'x {A3} - abs d3! ' 294 | f'x {A4} - abs d4! ' 295 | f'x {A5} - abs d5! ' 296 | f'x {A6} - abs d6! ' 297 | f'x {A7} - abs d7! ' 298 | f'x {A8} - abs d8! ' 299 | 'd1@ d2@ min mindiff! ' 300 | 'd1@ d2@ max maxdiff! ' 301 | 'maxdiff@ mindiff@ d3@ clamp maxdiff! ' 302 | 'mindiff@ d3@ min mindiff! ' 303 | 'maxdiff@ mindiff@ d4@ clamp maxdiff! ' 304 | 'mindiff@ d4@ min mindiff! ' 305 | 'maxdiff@ mindiff@ d5@ clamp maxdiff! ' 306 | 'mindiff@ d5@ min mindiff! ' 307 | 'maxdiff@ mindiff@ d6@ clamp maxdiff! ' 308 | 'mindiff@ d6@ min mindiff! ' 309 | 'maxdiff@ mindiff@ d7@ clamp maxdiff! ' 310 | 'mindiff@ d7@ min mindiff! ' 311 | 'maxdiff@ mindiff@ d8@ clamp maxdiff! ' 312 | 'y x maxdiff@ - x maxdiff@ + clamp' 313 | ) 314 | 315 | 316 | def aka_repair_expr_24() -> str: 317 | return ( 318 | f'{A1} {A8} max x - x {A1} {A8} min - max {A2} {A7} max x - x {A2} {A7} min - max ' 319 | f'{A3} {A6} max x - x {A3} {A6} min - max {A4} {A5} max x - x {A4} {A5} min - max min min min u! ' 320 | 'y x u@ - x u@ + clamp' 321 | ) 322 | 323 | 324 | def aka_repair_expr_26() -> str: 325 | return ( 326 | f'{A1} {A2} min mil1! ' 327 | f'{A1} {A2} max mal1! ' 328 | f'{A2} {A3} min mil2! ' 329 | f'{A2} {A3} max mal2! ' 330 | f'{A3} {A5} min mil3! ' 331 | f'{A3} {A5} max mal3! ' 332 | f'{A5} {A8} min mil4! ' 333 | f'{A5} {A8} max mal4! ' 334 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 335 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 336 | f'{A7} {A8} min mil1! ' 337 | f'{A7} {A8} max mal1! ' 338 | f'{A6} {A7} min mil2! ' 339 | f'{A6} {A7} max mal2! ' 340 | f'{A4} {A6} min mil3! ' 341 | f'{A4} {A6} max mal3! ' 342 | f'{A1} {A4} min mil4! ' 343 | f'{A1} {A4} max mal4! ' 344 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 345 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 346 | 'x y maxmil@ minmal@ min min y maxmil@ minmal@ max max clamp' 347 | ) 348 | 349 | 350 | def aka_repair_expr_27() -> str: 351 | return ( 352 | f'{A1} {A8} min mil1! ' 353 | f'{A1} {A8} max mal1! ' 354 | f'{A1} {A2} min mil2! ' 355 | f'{A1} {A2} max mal2! ' 356 | f'{A7} {A8} min mil3! ' 357 | f'{A7} {A8} max mal3! ' 358 | f'{A2} {A7} min mil4! ' 359 | f'{A2} {A7} max mal4! ' 360 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 361 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 362 | f'{A2} {A3} min mil1! ' 363 | f'{A2} {A3} max mal1! ' 364 | f'{A6} {A7} min mil2! ' 365 | f'{A6} {A7} max mal2! ' 366 | f'{A3} {A6} min mil3! ' 367 | f'{A3} {A6} max mal3! ' 368 | f'{A3} {A5} min mil4! ' 369 | f'{A3} {A5} max mal4! ' 370 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 371 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 372 | f'{A4} {A6} min mil1! ' 373 | f'{A4} {A6} max mal1! ' 374 | f'{A4} {A5} min mil2! ' 375 | f'{A4} {A5} max mal2! ' 376 | f'{A5} {A8} min mil3! ' 377 | f'{A5} {A8} max mal3! ' 378 | f'{A1} {A4} min mil4! ' 379 | f'{A1} {A4} max mal4! ' 380 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 381 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 382 | 'x y maxmil@ minmal@ min min y maxmil@ minmal@ max max clamp' 383 | ) 384 | 385 | 386 | def aka_repair_expr_28() -> str: 387 | return ( 388 | f'{A1} {A2} min mil1! ' 389 | f'{A1} {A2} max mal1! ' 390 | f'{A2} {A3} min mil2! ' 391 | f'{A2} {A3} max mal2! ' 392 | f'{A3} {A5} min mil3! ' 393 | f'{A3} {A5} max mal3! ' 394 | f'{A5} {A8} min mil4! ' 395 | f'{A5} {A8} max mal4! ' 396 | 'mil1@ mil2@ mil3@ mil4@ max max max maxmil! ' 397 | 'mal1@ mal2@ mal3@ mal4@ min min min minmal! ' 398 | f'{A7} {A8} min mil1! ' 399 | f'{A7} {A8} max mal1! ' 400 | f'{A6} {A7} min mil2! ' 401 | f'{A6} {A7} max mal2! ' 402 | f'{A4} {A6} min mil3! ' 403 | f'{A4} {A6} max mal3! ' 404 | f'{A1} {A5} min mil4! ' 405 | f'{A1} {A5} max mal4! ' 406 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 407 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 408 | f'{A1} {A8} min mil1! ' 409 | f'{A1} {A8} max mal1! ' 410 | f'{A3} {A6} min mil2! ' 411 | f'{A3} {A6} max mal2! ' 412 | f'{A2} {A7} min mil3! ' 413 | f'{A2} {A7} max mal3! ' 414 | f'{A4} {A5} min mil4! ' 415 | f'{A4} {A5} max mal4! ' 416 | 'mil1@ mil2@ mil3@ mil4@ maxmil@ max max max max maxmil! ' 417 | 'mal1@ mal2@ mal3@ mal4@ minmal@ min min min min minmal! ' 418 | 'x y maxmil@ minmal@ min min y maxmil@ minmal@ max max clamp' 419 | ) 420 | -------------------------------------------------------------------------------- /vsrgtools/blur.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from functools import partial 4 | from itertools import count 5 | from typing import Any, Literal, overload 6 | 7 | from vsexprtools import ExprOp, ExprVars, complexpr_available, norm_expr 8 | from vskernels import Bilinear, Gaussian 9 | from vstools import ( 10 | ConvMode, CustomValueError, FunctionUtil, OneDimConvModeT, PlanesT, SpatialConvModeT, 11 | TempConvModeT, check_variable, core, depth, get_depth, join, normalize_planes, normalize_seq, split, to_arr, vs 12 | ) 13 | 14 | from .enum import BlurMatrix, BlurMatrixBase, LimitFilterMode 15 | from .freqs import MeanMode 16 | from .limit import limit_filter 17 | from .util import normalize_radius 18 | 19 | __all__ = [ 20 | 'box_blur', 'side_box_blur', 21 | 'gauss_blur', 22 | 'min_blur', 'sbr', 'median_blur', 23 | 'bilateral', 'flux_smooth' 24 | ] 25 | 26 | 27 | def box_blur( 28 | clip: vs.VideoNode, radius: int | list[int] = 1, passes: int = 1, 29 | mode: OneDimConvModeT | TempConvModeT = ConvMode.HV, planes: PlanesT = None, **kwargs: Any 30 | ) -> vs.VideoNode: 31 | assert check_variable(clip, box_blur) 32 | 33 | planes = normalize_planes(clip, planes) 34 | 35 | if isinstance(radius, list): 36 | return normalize_radius(clip, box_blur, radius, planes, passes=passes) 37 | 38 | if not radius: 39 | return clip 40 | 41 | if mode == ConvMode.TEMPORAL or (clip.format.sample_type == vs.FLOAT and clip.format.bits_per_sample == 16): 42 | return BlurMatrix.MEAN(radius, mode=mode)(clip, planes, passes=passes, **kwargs) 43 | 44 | box_args = ( 45 | planes, 46 | radius, 0 if mode == ConvMode.VERTICAL else passes, 47 | radius, 0 if mode == ConvMode.HORIZONTAL else passes 48 | ) 49 | 50 | if hasattr(core, 'vszip'): 51 | return clip.vszip.BoxBlur(*box_args) 52 | 53 | if radius > 12: 54 | return clip.std.BoxBlur(*box_args) 55 | 56 | return BlurMatrix.MEAN(radius, mode=mode)(clip, planes, passes=passes, **kwargs) 57 | 58 | 59 | def side_box_blur( 60 | clip: vs.VideoNode, radius: int | list[int] = 1, planes: PlanesT = None, 61 | inverse: bool = False 62 | ) -> vs.VideoNode: 63 | planes = normalize_planes(clip, planes) 64 | 65 | if isinstance(radius, list): 66 | return normalize_radius(clip, side_box_blur, radius, planes, inverse=inverse) 67 | 68 | half_kernel = [(1 if i <= 0 else 0) for i in range(-radius, radius + 1)] 69 | 70 | conv_m1 = partial(core.std.Convolution, matrix=half_kernel, planes=planes) 71 | conv_m2 = partial(core.std.Convolution, matrix=half_kernel[::-1], planes=planes) 72 | blur_pt = partial(box_blur, planes=planes) 73 | 74 | vrt_filters, hrz_filters = [ 75 | [ 76 | partial(conv_m1, mode=mode), partial(conv_m2, mode=mode), 77 | partial(blur_pt, hradius=hr, vradius=vr, hpasses=h, vpasses=v) 78 | ] for h, hr, v, vr, mode in [ 79 | (0, None, 1, radius, ConvMode.VERTICAL), (1, radius, 0, None, ConvMode.HORIZONTAL) 80 | ] 81 | ] 82 | 83 | vrt_intermediates = (vrt_flt(clip) for vrt_flt in vrt_filters) 84 | intermediates = list( 85 | hrz_flt(vrt_intermediate) 86 | for i, vrt_intermediate in enumerate(vrt_intermediates) 87 | for j, hrz_flt in enumerate(hrz_filters) if not i == j == 2 88 | ) 89 | 90 | comp_blur = None if inverse else box_blur(clip, radius, 1, planes=planes) 91 | 92 | if complexpr_available: 93 | template = '{cum} x - abs {new} x - abs < {cum} {new} ?' 94 | 95 | cum_expr, cumc = '', 'y' 96 | n_inter = len(intermediates) 97 | 98 | for i, newc, var in zip(count(), ExprVars[2:26], ExprVars[4:26]): 99 | if i == n_inter - 1: 100 | break 101 | 102 | cum_expr += template.format(cum=cumc, new=newc) 103 | 104 | if i != n_inter - 2: 105 | cumc = var.upper() 106 | cum_expr += f' {cumc}! ' 107 | cumc = f'{cumc}@' 108 | 109 | if comp_blur: 110 | clips = [clip, *intermediates, comp_blur] 111 | cum_expr = f'x {cum_expr} - {ExprVars[n_inter + 1]} +' 112 | else: 113 | clips = [clip, *intermediates] 114 | 115 | cum = norm_expr(clips, cum_expr, planes, force_akarin='vsrgtools.side_box_blur') 116 | else: 117 | cum = intermediates[0] 118 | for new in intermediates[1:]: 119 | cum = limit_filter(clip, cum, new, LimitFilterMode.SIMPLE2_MIN, planes) 120 | 121 | if comp_blur: 122 | cum = clip.std.MakeDiff(cum).std.MergeDiff(comp_blur) 123 | 124 | if comp_blur: 125 | return box_blur(cum, 1, min(radius // 2, 1)) 126 | 127 | return cum 128 | 129 | 130 | def gauss_blur( 131 | clip: vs.VideoNode, sigma: float | list[float] = 0.5, taps: int | None = None, 132 | mode: ConvMode = ConvMode.HV, planes: PlanesT = None, 133 | **kwargs: Any 134 | ) -> vs.VideoNode: 135 | assert check_variable(clip, gauss_blur) 136 | 137 | planes = normalize_planes(clip, planes) 138 | 139 | if isinstance(sigma, list): 140 | return normalize_radius(clip, gauss_blur, ('sigma', sigma), planes, mode=mode) 141 | 142 | if ConvMode.VERTICAL in mode: 143 | sigma = min(sigma, clip.height) 144 | 145 | if ConvMode.HORIZONTAL in mode: 146 | sigma = min(sigma, clip.width) 147 | 148 | taps = BlurMatrix.GAUSS.get_taps(sigma, taps) 149 | 150 | if hasattr(core, 'resize2') and not mode.is_temporal: 151 | def _resize2_blur(plane: vs.VideoNode, sigma: float, taps: int) -> vs.VideoNode: 152 | resize_kwargs = dict[str, Any]() 153 | 154 | # Downscale approximation can be used by specifying _fast=True 155 | # Has a big speed gain when taps is large 156 | if kwargs.pop("_fast", False): 157 | wdown, hdown = plane.width, plane.height 158 | 159 | if ConvMode.VERTICAL in mode: 160 | hdown = round(max(round(hdown / sigma), 2) / 2) * 2 161 | 162 | if ConvMode.HORIZONTAL in mode: 163 | wdown = round(max(round(wdown / sigma), 2) / 2) * 2 164 | 165 | resize_kwargs.update(width=plane.width, height=plane.height) 166 | 167 | plane = Bilinear.scale(plane, wdown, hdown) 168 | sigma = 0.8952637851149309 169 | taps = min(taps, 128) 170 | else: 171 | resize_kwargs.update({f'force_{k}': k in mode for k in 'hv'}) 172 | 173 | return Gaussian(sigma, taps).scale(plane, **resize_kwargs | kwargs) 174 | 175 | if not {*range(clip.format.num_planes)} - {*planes}: 176 | return _resize2_blur(clip, sigma, taps) 177 | 178 | return join([ 179 | _resize2_blur(p, sigma, taps) if i in planes else p 180 | for i, p in enumerate(split(clip)) 181 | ]) 182 | 183 | kernel: BlurMatrixBase[float] = BlurMatrix.GAUSS( # type: ignore 184 | taps, sigma=sigma, mode=mode, scale_value=1.0 if taps > 12 else 1023 185 | ) 186 | 187 | return kernel(clip, planes, **kwargs) 188 | 189 | 190 | def min_blur( 191 | clip: vs.VideoNode, radius: int | list[int] = 1, 192 | mode: tuple[ConvMode, ConvMode] = (ConvMode.HV, ConvMode.SQUARE), planes: PlanesT = None, 193 | **kwargs: Any 194 | ) -> vs.VideoNode: 195 | """ 196 | MinBlur by Didée (http://avisynth.nl/index.php/MinBlur) 197 | Nifty Gauss/Median combination 198 | """ 199 | assert check_variable(clip, min_blur) 200 | 201 | planes = normalize_planes(clip, planes) 202 | 203 | if isinstance(radius, list): 204 | return normalize_radius(clip, min_blur, radius, planes) 205 | 206 | mode_blur, mode_median = normalize_seq(mode, 2) 207 | 208 | blurred = BlurMatrix.BINOMIAL(radius=radius, mode=mode_blur)(clip, planes=planes, **kwargs) 209 | median = median_blur(clip, radius, mode_median, planes=planes) 210 | 211 | return MeanMode.MEDIAN([clip, blurred, median], planes=planes) 212 | 213 | 214 | def sbr( 215 | clip: vs.VideoNode, radius: int | list[int] = 1, 216 | mode: ConvMode = ConvMode.HV, planes: PlanesT = None, 217 | **kwargs: Any 218 | ) -> vs.VideoNode: 219 | assert check_variable(clip, sbr) 220 | 221 | planes = normalize_planes(clip, planes) 222 | 223 | blur_kernel = BlurMatrix.BINOMIAL(radius=radius, mode=mode) 224 | 225 | blurred = blur_kernel(clip, planes=planes, **kwargs) 226 | 227 | diff = clip.std.MakeDiff(blurred, planes=planes) 228 | blurred_diff = blur_kernel(diff, planes=planes, **kwargs) 229 | 230 | return norm_expr( 231 | [clip, diff, blurred_diff], 232 | 'y z - D1! y neutral - D2! x D1@ D2@ xor 0 D1@ abs D2@ abs < D1@ D2@ ? ? -', 233 | planes=planes 234 | ) 235 | 236 | 237 | @overload 238 | def median_blur( 239 | clip: vs.VideoNode, radius: int = ..., mode: Literal[ConvMode.TEMPORAL] = ..., planes: PlanesT = ... 240 | ) -> vs.VideoNode: 241 | ... 242 | 243 | 244 | @overload 245 | def median_blur( 246 | clip: vs.VideoNode, radius: int | list[int] = ..., mode: SpatialConvModeT = ..., planes: PlanesT = None 247 | ) -> vs.VideoNode: 248 | ... 249 | 250 | 251 | @overload 252 | def median_blur( 253 | clip: vs.VideoNode, radius: int | list[int] = ..., mode: ConvMode = ..., planes: PlanesT = None 254 | ) -> vs.VideoNode: 255 | ... 256 | 257 | 258 | def median_blur( 259 | clip: vs.VideoNode, radius: int | list[int] = 1, mode: ConvMode = ConvMode.SQUARE, planes: PlanesT = None 260 | ) -> vs.VideoNode: 261 | if mode == ConvMode.TEMPORAL: 262 | if isinstance(radius, int): 263 | return clip.zsmooth.TemporalMedian(radius, planes) 264 | 265 | raise CustomValueError("A list of radius isn't supported for ConvMode.TEMPORAL!", median_blur, radius) 266 | 267 | radius = to_arr(radius) 268 | 269 | if (len((rs := set(radius))) == 1 and rs.pop() == 1) and mode == ConvMode.SQUARE: 270 | return clip.std.Median(planes=planes) 271 | 272 | expr_plane = list[list[str]]() 273 | 274 | for r in radius: 275 | expr_passes = list[str]() 276 | 277 | for mat in ExprOp.matrix('x', r, mode, [(0, 0)]): 278 | rb = len(mat) + 1 279 | st = rb - 1 280 | sp = rb // 2 - 1 281 | dp = st - 2 282 | 283 | expr_passes.append(f"{mat} sort{st} swap{sp} min! swap{sp} max! drop{dp} x min@ max@ clip") 284 | 285 | expr_plane.append(expr_passes) 286 | 287 | for e in zip(*expr_plane): 288 | clip = norm_expr(clip, e, planes, force_akarin=median_blur) 289 | 290 | return clip 291 | 292 | 293 | def bilateral( 294 | clip: vs.VideoNode, sigmaS: float | list[float] = 3.0, sigmaR: float | list[float] = 0.02, 295 | ref: vs.VideoNode | None = None, radius: int | list[int] | None = None, 296 | device_id: int = 0, num_streams: int | None = None, use_shared_memory: bool = True, 297 | block_x: int | None = None, block_y: int | None = None, planes: PlanesT = None, 298 | *, gpu: bool | None = None 299 | ) -> vs.VideoNode: 300 | func = FunctionUtil(clip, bilateral, planes) 301 | 302 | sigmaS, sigmaR = func.norm_seq(sigmaS), func.norm_seq(sigmaR) 303 | 304 | if gpu is not False: 305 | basic_args, new_args = (sigmaS, sigmaR, radius, device_id), (num_streams, use_shared_memory) 306 | 307 | if hasattr(core, 'bilateralgpu_rtc'): 308 | return clip.bilateralgpu_rtc.Bilateral(*basic_args, *new_args, block_x, block_y, ref) 309 | else: 310 | return clip.bilateralgpu.Bilateral(*basic_args, *new_args, ref) 311 | 312 | if (bits := get_depth(clip)) > 16: 313 | clip = depth(clip, 16) 314 | 315 | if ref and clip.format != ref.format: 316 | ref = depth(ref, clip) 317 | 318 | clip = clip.vszip.Bilateral(ref, sigmaS, sigmaR) 319 | 320 | return depth(clip, bits) 321 | 322 | 323 | def flux_smooth( 324 | clip: vs.VideoNode, temporal_threshold: float = 7.0, spatial_threshold: float = 0.0, 325 | scalep: bool = True, planes: PlanesT = None 326 | ) -> vs.VideoNode: 327 | func = FunctionUtil(clip, flux_smooth, planes) 328 | 329 | if spatial_threshold: 330 | smoothed = func.work_clip.zsmooth.FluxSmoothST(temporal_threshold, spatial_threshold, scalep) 331 | else: 332 | smoothed = func.work_clip.zsmooth.FluxSmoothT(temporal_threshold, scalep) 333 | 334 | return func.return_clip(smoothed) 335 | -------------------------------------------------------------------------------- /vsrgtools/contra.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from functools import partial 4 | from inspect import Signature 5 | from typing import Callable 6 | 7 | from vsexprtools import complexpr_available, norm_expr 8 | from vstools import ( 9 | CustomValueError, GenericVSFunction, PlanesT, check_ref_clip, check_variable, clamp_arr, get_neutral_value, 10 | iterate, normalize_planes, to_arr, vs, core 11 | ) 12 | 13 | from .blur import box_blur, median_blur, min_blur 14 | from .enum import BlurMatrix, RemoveGrainMode, RemoveGrainModeT, RepairMode, RepairModeT 15 | from .rgtools import removegrain, repair 16 | from .util import norm_rmode_planes 17 | 18 | __all__ = [ 19 | 'contrasharpening', 'contra', 20 | 'contrasharpening_dehalo', 'contra_dehalo', 21 | 'contrasharpening_median', 'contra_median', 22 | 'fine_contra' 23 | ] 24 | 25 | 26 | def contrasharpening( 27 | flt: vs.VideoNode, src: vs.VideoNode, radius: int | list[int] = 1, 28 | sharp: vs.VideoNode | GenericVSFunction | None = None, 29 | mode: RepairModeT = RepairMode.MINMAX_SQUARE3, planes: PlanesT = 0 30 | ) -> vs.VideoNode: 31 | """ 32 | contra-sharpening: sharpen the denoised clip, but don't add more to any pixel than what was previously removed. 33 | Script by Didée, at the VERY GRAINY thread (http://forum.doom9.org/showthread.php?p=1076491#post1076491) 34 | 35 | :param flt: Filtered clip 36 | :param src: Source clip 37 | :param radius: Spatial radius for sharpening. 38 | :param sharp: Optional pre-sharpened clip or function to use. 39 | :param mode: Mode of rgvs.Repair to limit the difference 40 | :param planes: Planes to process, defaults to None 41 | 42 | :return: Contrasharpened clip 43 | """ 44 | 45 | assert check_variable(src, contrasharpening) 46 | assert check_variable(flt, contrasharpening) 47 | check_ref_clip(src, flt, contrasharpening) 48 | 49 | planes = normalize_planes(flt, planes) 50 | 51 | # Damp down remaining spots of the denoised clip 52 | if isinstance(sharp, vs.VideoNode): 53 | sharpened = sharp 54 | elif callable(sharp): 55 | sharpened = sharp(flt) 56 | else: 57 | damp = min_blur(flt, radius, planes=planes) 58 | blurred = BlurMatrix.BINOMIAL(radius=radius)(damp, planes=planes) 59 | 60 | # Difference of a simple kernel blur 61 | diff_blur = core.std.MakeDiff( 62 | sharpened if sharp else damp, 63 | flt if sharp else blurred, 64 | planes 65 | ) 66 | 67 | # Difference achieved by the filtering 68 | diff_flt = src.std.MakeDiff(flt, planes) 69 | 70 | # Limit the difference to the max of what the filtering removed locally 71 | limit = repair(diff_blur, diff_flt, norm_rmode_planes(flt, mode, planes)) 72 | 73 | # abs(diff) after limiting may not be bigger than before 74 | # Apply the limited difference (sharpening is just inverse blurring) 75 | expr = 'x neutral - X! y neutral - Y! X@ abs Y@ abs < X@ Y@ ? z +' 76 | 77 | return norm_expr([limit, diff_blur, flt], expr, planes) 78 | 79 | 80 | def contrasharpening_dehalo( 81 | flt: vs.VideoNode, src: vs.VideoNode, level: float = 1.4, alpha: float = 2.49, planes: PlanesT = 0 82 | ) -> vs.VideoNode: 83 | """ 84 | :param dehaloed: Dehaloed clip 85 | :param src: Source clip 86 | :param level: Strength level 87 | :return: Contrasharpened clip 88 | """ 89 | assert check_variable(src, contrasharpening) 90 | assert check_variable(flt, contrasharpening) 91 | check_ref_clip(src, flt, contrasharpening) 92 | 93 | planes = normalize_planes(flt, planes) 94 | 95 | rep_modes = norm_rmode_planes(flt, RepairMode.MINMAX_SQUARE1, planes) 96 | 97 | blur = BlurMatrix.BINOMIAL()(flt, planes) 98 | blur2 = median_blur(blur, 2, planes=planes) 99 | blur2 = iterate(blur2, partial(repair, repairclip=blur), 2, mode=rep_modes) 100 | 101 | return norm_expr( 102 | [blur, blur2, src, flt], 103 | 'x y - {alpha} * {level} * D1! z a - D2! D1@ D2@ xor 0 D1@ abs D2@ abs < D1@ D2@ ? ? a +', 104 | planes, alpha=alpha, level=level 105 | ) 106 | 107 | 108 | def contrasharpening_median( 109 | flt: vs.VideoNode, src: vs.VideoNode, 110 | mode: RemoveGrainModeT | Callable[..., vs.VideoNode] = box_blur, 111 | planes: PlanesT = 0 112 | ) -> vs.VideoNode: 113 | """ 114 | :param flt: Filtered clip 115 | :param src: Source clip 116 | :param mode: Function or the RemoveGrain mode used to blur/repair the filtered clip. 117 | :param planes: Planes to process, defaults to None 118 | :return: Contrasharpened clip 119 | """ 120 | assert check_variable(src, contrasharpening) 121 | assert check_variable(flt, contrasharpening) 122 | check_ref_clip(src, flt, contrasharpening) 123 | 124 | planes = normalize_planes(flt, planes) 125 | 126 | if isinstance(mode, (int, list, RemoveGrainMode)): 127 | repaired = removegrain(flt, norm_rmode_planes(flt, mode, planes)) 128 | elif callable(mode): 129 | repaired = mode(flt, planes=planes) 130 | else: 131 | raise CustomValueError('Invalid mode or function passed!', contrasharpening_median) 132 | 133 | if complexpr_available: 134 | expr = 'x dup + z - D! x y < D@ x y clamp D@ y x clamp ?' 135 | else: 136 | expr = 'x dup + z - x y min max x y max min' 137 | 138 | return norm_expr([flt, src, repaired], expr, planes) 139 | 140 | 141 | def fine_contra( 142 | flt: vs.VideoNode, src: vs.VideoNode, sharp: float | list[float] | range = 0.75, 143 | radius: int | list[int] = 1, merge_func: GenericVSFunction | None = None, 144 | mode: RepairModeT = RepairMode.MINMAX_SQUARE_REF3, planes: PlanesT = 0 145 | ) -> vs.VideoNode: 146 | """ 147 | :param flt: Filtered clip. 148 | :param src: Source clip. 149 | :param sharp: Contrast Adaptive Sharpening's sharpening strength. 150 | If it's a list, depending on ``merge_func`` being ``None``, 151 | it will iterate over with different strengths or merge all with ``merge_func``. 152 | :param radius: Spatial radius for contra-sharpening (1-3). Default is 2 for HD / 1 for SD. 153 | :param merge_func: Depending on ``sharp``, this will get all sharpened clips and merge them. 154 | :param mode: Mode of rgvs.Repair to limit the difference. 155 | :param planes: Planes to process, defaults to None. 156 | :return: Contrasharpened clip. 157 | """ 158 | 159 | assert check_variable(src, contrasharpening) 160 | assert check_variable(flt, contrasharpening) 161 | check_ref_clip(src, flt, contrasharpening) 162 | 163 | neutral = get_neutral_value(flt) 164 | 165 | planes = normalize_planes(flt, planes) 166 | 167 | mblur = min_blur(flt, radius, planes=planes) 168 | 169 | sharp = [1.0 / x for x in sharp if x] if isinstance(sharp, range) else to_arr(sharp) 170 | sharp = clamp_arr(sharp, 0.0, 1.0) 171 | 172 | if merge_func is None: 173 | for s in sharp: 174 | mblur = mblur.cas.CAS(s, planes) 175 | else: 176 | mblurs = [mblur.cas.CAS(s, planes) for s in sharp] 177 | 178 | got_p = 'planes' in Signature.from_callable(merge_func).parameters.keys() 179 | 180 | try: 181 | if got_p: 182 | mblur = merge_func(*mblurs, planes=planes) 183 | else: 184 | mblur = merge_func(*mblurs) 185 | except Exception: 186 | if got_p: 187 | mblur = merge_func(mblurs, planes=planes) 188 | else: 189 | mblur = merge_func(mblurs) 190 | 191 | limit = repair(mblur, src.std.MakeDiff(flt, planes), norm_rmode_planes(flt, mode, planes)) 192 | 193 | if complexpr_available: 194 | expr = 'x {mid} - LD! y {mid} - BD! LD@ abs BD@ abs < LD@ BD@ ? z +' 195 | else: 196 | expr = 'x {mid} - abs y {mid} - abs < x y ? {mid} - z +' 197 | 198 | return norm_expr([limit, mblur, flt], expr, planes, mid=neutral) 199 | 200 | 201 | contra = contrasharpening 202 | contra_dehalo = contrasharpening_dehalo 203 | contra_median = contrasharpening_median 204 | -------------------------------------------------------------------------------- /vsrgtools/enum.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import operator 4 | 5 | from enum import auto 6 | from itertools import accumulate 7 | from math import ceil, exp, log2, pi, sqrt 8 | from typing import Any, Iterable, Literal, Self, Sequence, overload 9 | 10 | from vsexprtools import ExprList, ExprOp, ExprToken, ExprVars 11 | from vstools import ( 12 | ConvMode, CustomIntEnum, CustomValueError, KwargsT, Nb, PlanesT, check_variable, core, fallback, 13 | iterate, shift_clip_multi, to_singleton, vs 14 | ) 15 | 16 | __all__ = [ 17 | 'LimitFilterMode', 18 | 'RemoveGrainMode', 'RemoveGrainModeT', 19 | 'RepairMode', 'RepairModeT', 20 | 'VerticalCleanerMode', 'VerticalCleanerModeT', 21 | 'BlurMatrixBase', 'BlurMatrix' 22 | ] 23 | 24 | 25 | class LimitFilterModeMeta: 26 | force_expr = True 27 | 28 | 29 | class LimitFilterMode(LimitFilterModeMeta, CustomIntEnum): 30 | """Two sources, one filtered""" 31 | SIMPLE_MIN = auto() 32 | SIMPLE_MAX = auto() 33 | """One source, two filtered""" 34 | SIMPLE2_MIN = auto() 35 | SIMPLE2_MAX = auto() 36 | DIFF_MIN = auto() 37 | DIFF_MAX = auto() 38 | """One/Two sources, one filtered""" 39 | CLAMPING = auto() 40 | 41 | @property 42 | def op(self) -> str: 43 | return '<' if 'MIN' in self._name_ else '>' 44 | 45 | def __call__(self, force_expr: bool = True) -> LimitFilterMode: 46 | self.force_expr = force_expr 47 | 48 | return self 49 | 50 | 51 | class RemoveGrainMode(CustomIntEnum): 52 | NONE = 0 53 | MINMAX_AROUND1 = 1 54 | MINMAX_AROUND2 = 2 55 | MINMAX_AROUND3 = 3 56 | MINMAX_MEDIAN = 4 57 | EDGE_CLIP_STRONG = 5 58 | EDGE_CLIP_MODERATE = 6 59 | EDGE_CLIP_MEDIUM = 7 60 | EDGE_CLIP_LIGHT = 8 61 | LINE_CLIP_CLOSE = 9 62 | MIN_SHARP = 10 63 | BINOMIAL_BLUR = 11 64 | BOB_TOP_CLOSE = 13 65 | BOB_BOTTOM_CLOSE = 14 66 | BOB_TOP_INTER = 15 67 | BOB_BOTTOM_INTER = 16 68 | MINMAX_MEDIAN_OPP = 17 69 | LINE_CLIP_OPP = 18 70 | BOX_BLUR_NO_CENTER = 19 71 | BOX_BLUR = 20 72 | OPP_CLIP_AVG = 21 73 | OPP_CLIP_AVG_FAST = 22 74 | EDGE_DEHALO = 23 75 | EDGE_DEHALO2 = 24 76 | MIN_SHARP2 = 25 77 | SMART_RGC = 26 78 | SMART_RGCL = 27 79 | SMART_RGCL2 = 28 80 | 81 | def __call__(self, clip: vs.VideoNode, planes: PlanesT = None) -> vs.VideoNode: 82 | from .rgtools import removegrain 83 | from .util import norm_rmode_planes 84 | return removegrain(clip, norm_rmode_planes(clip, self, planes)) 85 | 86 | 87 | RemoveGrainModeT = int | RemoveGrainMode | Sequence[int | RemoveGrainMode] 88 | 89 | 90 | class RepairMode(CustomIntEnum): 91 | NONE = 0 92 | MINMAX_SQUARE1 = 1 93 | MINMAX_SQUARE2 = 2 94 | MINMAX_SQUARE3 = 3 95 | MINMAX_SQUARE4 = 4 96 | LINE_CLIP_MIN = 5 97 | LINE_CLIP_LIGHT = 6 98 | LINE_CLIP_MEDIUM = 7 99 | LINE_CLIP_STRONG = 8 100 | LINE_CLIP_CLOSE = 9 101 | MINMAX_SQUARE_REF_CLOSE = 10 102 | MINMAX_SQUARE_REF1 = 11 103 | MINMAX_SQUARE_REF2 = 12 104 | MINMAX_SQUARE_REF3 = 13 105 | MINMAX_SQUARE_REF4 = 14 106 | CLIP_REF_RG5 = 15 107 | CLIP_REF_RG6 = 16 108 | CLIP_REF_RG17 = 17 109 | CLIP_REF_RG18 = 18 110 | CLIP_REF_RG19 = 19 111 | CLIP_REF_RG20 = 20 112 | CLIP_REF_RG21 = 21 113 | CLIP_REF_RG22 = 22 114 | CLIP_REF_RG23 = 23 115 | CLIP_REF_RG24 = 24 116 | CLIP_REF_RG26 = 26 117 | CLIP_REF_RG27 = 27 118 | CLIP_REF_RG28 = 28 119 | 120 | def __call__(self, clip: vs.VideoNode, repairclip: vs.VideoNode, planes: PlanesT = None) -> vs.VideoNode: 121 | from .rgtools import repair 122 | from .util import norm_rmode_planes 123 | return repair(clip, repairclip, norm_rmode_planes(clip, self, planes)) 124 | 125 | 126 | RepairModeT = int | RepairMode | Sequence[int | RepairMode] 127 | 128 | 129 | class VerticalCleanerMode(CustomIntEnum): 130 | NONE = 0 131 | MEDIAN = 1 132 | PRESERVING = 2 133 | 134 | def __call__(self, clip: vs.VideoNode, planes: PlanesT = None) -> vs.VideoNode: 135 | from .rgtools import vertical_cleaner 136 | from .util import norm_rmode_planes 137 | return vertical_cleaner(clip, norm_rmode_planes(clip, self, planes)) 138 | 139 | 140 | VerticalCleanerModeT = int | VerticalCleanerMode | Sequence[int | VerticalCleanerMode] 141 | 142 | 143 | class BlurMatrixBase(list[Nb]): 144 | def __init__( 145 | self, __iterable: Iterable[Nb], /, mode: ConvMode = ConvMode.SQUARE, 146 | ) -> None: 147 | self.mode = mode 148 | super().__init__(__iterable) # type: ignore[arg-type] 149 | 150 | def __call__( 151 | self, clip: vs.VideoNode, planes: PlanesT = None, 152 | bias: float | None = None, divisor: float | None = None, saturate: bool = True, 153 | passes: int = 1, expr_kwargs: KwargsT | None = None, **conv_kwargs: Any 154 | ) -> vs.VideoNode: 155 | """ 156 | Performs a spatial or temporal convolution. 157 | It will either calls std.Convolution, std.AverageFrames or ExprOp.convolution 158 | based on the ConvMode mode picked. 159 | 160 | :param clip: Clip to process. 161 | :param planes: Specifies which planes will be processed. 162 | :param bias: Value to add to the final result of the convolution 163 | (before clamping the result to the format's range of valid values). 164 | :param divisor: Divide the output of the convolution by this value (before adding bias). 165 | The default is the sum of the elements of the matrix 166 | :param saturate: If True, negative values become 0. 167 | If False, absolute values are returned. 168 | :param passes: Number of iterations. 169 | :param expr_kwargs: A KwargsT of keyword arguments for ExprOp.convolution.__call__ when it is picked. 170 | :param **conv_kwargs: Additional keyword arguments for std.Convolution, std.AverageFrames or ExprOp.convolution. 171 | 172 | :return: Processed clip. 173 | """ 174 | if len(self) <= 1: 175 | return clip 176 | 177 | assert (check_variable(clip, self.__call__)) 178 | 179 | expr_kwargs = expr_kwargs or KwargsT() 180 | 181 | fp16 = clip.format.sample_type == vs.FLOAT and clip.format.bits_per_sample == 16 182 | 183 | if self.mode.is_spatial: 184 | # std.Convolution is limited to 25 numbers 185 | # SQUARE mode is not optimized 186 | # std.Convolution doesn't support float 16 187 | if len(self) <= 25 and self.mode != ConvMode.SQUARE and not fp16: 188 | return iterate(clip, core.std.Convolution, passes, self, bias, divisor, planes, saturate, self.mode) 189 | 190 | return iterate( 191 | clip, ExprOp.convolution("x", self, bias, fallback(divisor, True), saturate, self.mode, **conv_kwargs), 192 | passes, planes=planes, **expr_kwargs 193 | ) 194 | 195 | if all([ 196 | not fp16, 197 | len(self) <= 31, 198 | not bias, 199 | saturate, 200 | (len(conv_kwargs) == 0 or (len(conv_kwargs) == 1 and "scenechange" in conv_kwargs)) 201 | ]): 202 | return iterate(clip, core.std.AverageFrames, passes, self, divisor, planes=planes, **conv_kwargs) 203 | 204 | return self._averageframes_akarin(clip, planes, bias, divisor, saturate, passes, expr_kwargs, **conv_kwargs) 205 | 206 | def _averageframes_akarin(self, *args: Any, **kwargs: Any) -> vs.VideoNode: 207 | clip, planes, bias, divisor, saturate, passes, expr_kwargs = args 208 | conv_kwargs = kwargs 209 | 210 | r = len(self) // 2 211 | 212 | if conv_kwargs.pop("scenechange", False) is False: 213 | expr_conv = ExprOp.convolution( 214 | ExprVars(len(self)), self, bias, fallback(divisor, True), saturate, self.mode, **conv_kwargs 215 | ) 216 | return iterate( 217 | clip, lambda x: expr_conv(shift_clip_multi(x, (-r, r)), planes=planes, **expr_kwargs), passes 218 | ) 219 | 220 | vars_, = ExprOp.matrix(ExprVars(len(self)), r, self.mode) 221 | 222 | # Conditionnal for backward frames 223 | condb = list([ExprList(["cond0!", "cond0@", 0])]) 224 | 225 | for i, vv in enumerate(accumulate(vars_[:r][::-1], operator.add), 1): 226 | ww = list[Any]() 227 | 228 | for j, (v, w) in enumerate(zip(vv, self[:r][::-1])): 229 | ww.append([v, w, ExprOp.DUP, f"div{j}!", ExprOp.MUL]) 230 | 231 | condb.append(ExprList([f"cond{i}!", f"cond{i}@", ww, [ExprOp.ADD] * (i - 1)])) 232 | 233 | # Conditionnal for forward frames 234 | condf = list([ExprList([f"cond{i + 1}!", f"cond{i + 1}@", 0])]) 235 | 236 | for ii, vv in enumerate(accumulate(vars_[r + 1:], operator.add), i + 2): 237 | ww = list[Any]() 238 | 239 | for jj, (v, w) in enumerate(zip(vv, self[r + 1:]), j + 2): 240 | ww.append([v, w, ExprOp.DUP, f"div{jj}!", ExprOp.MUL]) 241 | 242 | condf.append(ExprList([f"cond{ii}!", f"cond{ii}@", ww, [ExprOp.ADD] * (ii - i - 2)])) 243 | 244 | expr = ExprList() 245 | 246 | # Conditionnal for backward frames 247 | for i, (v, c) in enumerate(zip(vars_[:r][::-1], condb)): 248 | expr.append(f"{v}._SceneChangeNext", *c) 249 | 250 | expr.append(condb[-1][2:], ExprOp.TERN * r) 251 | 252 | # Conditionnal for forward frames 253 | for i, (v, c) in enumerate(zip(vars_[r + 1:], condf)): 254 | expr.append(f"{v}._SceneChangePrev", *c) 255 | 256 | expr.append(condf[-1][2:], ExprOp.TERN * r) 257 | 258 | expr.append(ExprOp.ADD, vars_[r], self[r]) 259 | 260 | # Center frame 261 | weights_cum_b = ExprList(v for v in reversed(list(accumulate(self[:r])))) 262 | weights_cum_f = ExprList(v for v in reversed(list(accumulate(self[r + 1:][::-1])))) 263 | 264 | for k, ws in zip(range(ii), weights_cum_b + ExprList() + weights_cum_f): 265 | if k == r: 266 | continue 267 | expr.append(f"cond{k}@", ws, ExprOp.MUL) 268 | 269 | expr.extend([ExprOp.ADD] * k) 270 | expr.append(ExprOp.DUP, f"div{r}!", ExprOp.MUL, ExprOp.ADD) 271 | 272 | if (premultiply := conv_kwargs.get("premultiply", None)): 273 | expr.append(premultiply, ExprOp.MUL) 274 | 275 | if divisor: 276 | expr.append(divisor, ExprOp.DIV) 277 | else: 278 | # Divisor conditionnal 279 | for cond, rr in zip([condb, condf], [(0, r), (r + 1, r * 2 + 1)]): 280 | for n, m in zip(accumulate(([d] for d in range(*rr)), initial=[0]), cond[:-1]): 281 | if (n := n[1:]): 282 | div = list[str]() 283 | for divn in n: 284 | div.append(f"div{divn}@") 285 | else: 286 | div = [str(0)] 287 | 288 | expr.append(str(m[0])[:5] + "@", div, [ExprOp.ADD] * max(0, len(n) - 1)) 289 | 290 | expr.append(*div, f"div{divn + 1}@", ExprOp.ADD * len(div)) 291 | expr.append(ExprOp.TERN * r) 292 | 293 | expr.append(ExprOp.ADD, f"div{r}@", ExprOp.ADD, ExprOp.DIV) 294 | 295 | if bias: 296 | expr.append(bias, ExprOp.ADD) 297 | 298 | if not saturate: 299 | expr.append(ExprOp.ABS) 300 | 301 | if (multiply := conv_kwargs.get("multiply", None)): 302 | expr.append(multiply, ExprOp.MUL) 303 | 304 | if conv_kwargs.get("clamp", False): 305 | expr.append(ExprOp.clamp(ExprToken.RangeMin, ExprToken.RangeMax)) 306 | 307 | return iterate(clip, lambda x: expr(shift_clip_multi(x, (-r, r)), planes=planes, **expr_kwargs), passes) 308 | 309 | def outer(self) -> Self: 310 | from numpy import outer 311 | 312 | return self.__class__(outer(self, self).flatten().tolist(), self.mode) 313 | 314 | 315 | class BlurMatrix(CustomIntEnum): 316 | CIRCLE = 0 317 | MEAN = 1 318 | BINOMIAL = 2 319 | LOG = 3 320 | 321 | @to_singleton.as_property 322 | class GAUSS: 323 | def __call__( 324 | self, 325 | taps: int | None = None, 326 | *, 327 | sigma: float = 0.5, 328 | mode: ConvMode = ConvMode.HV, 329 | **kwargs: Any 330 | ) -> BlurMatrixBase[float]: 331 | scale_value = kwargs.get("scale_value", 1023) 332 | 333 | if mode == ConvMode.SQUARE: 334 | scale_value = sqrt(scale_value) 335 | 336 | taps = self.get_taps(sigma, taps) 337 | 338 | if taps < 0: 339 | raise CustomValueError('Taps must be >= 0!') 340 | 341 | if sigma > 0.0: 342 | half_pisqrt = 1.0 / sqrt(2.0 * pi) * sigma 343 | doub_qsigma = 2 * sigma ** 2 344 | 345 | high, *mat = [half_pisqrt * exp(-x ** 2 / doub_qsigma) for x in range(taps + 1)] 346 | 347 | mat = [x * scale_value / high for x in mat] 348 | mat = [*mat[::-1], scale_value, *mat] 349 | else: 350 | mat = [scale_value] 351 | 352 | kernel = BlurMatrixBase(mat, mode) 353 | 354 | if mode == ConvMode.SQUARE: 355 | kernel = kernel.outer() 356 | 357 | return kernel 358 | 359 | def from_radius(self, radius: int) -> BlurMatrixBase[float]: 360 | return self(None, sigma=(radius + 1.0) / 3) 361 | 362 | @staticmethod 363 | def get_taps(sigma: float, taps: int | None = None) -> int: 364 | if taps is None: 365 | taps = ceil(abs(sigma) * 8 + 1) // 2 366 | 367 | return taps 368 | 369 | @overload 370 | def __call__( # type: ignore[misc] 371 | self: Literal[BlurMatrix.CIRCLE], taps: int = 1, *, mode: ConvMode = ConvMode.SQUARE 372 | ) -> BlurMatrixBase[int]: 373 | ... 374 | 375 | @overload 376 | def __call__( # type: ignore[misc] 377 | self: Literal[BlurMatrix.MEAN], taps: int = 1, *, mode: ConvMode = ConvMode.SQUARE 378 | ) -> BlurMatrixBase[int]: 379 | ... 380 | 381 | @overload 382 | def __call__( # type: ignore[misc] 383 | self: Literal[BlurMatrix.BINOMIAL], taps: int = 1, *, mode: ConvMode = ConvMode.HV, **kwargs: Any 384 | ) -> BlurMatrixBase[int]: 385 | ... 386 | 387 | @overload 388 | def __call__( # type: ignore[misc] 389 | self: Literal[BlurMatrix.LOG], taps: int = 1, *, strength: float = 100.0, mode: ConvMode = ConvMode.HV 390 | ) -> BlurMatrixBase[float]: 391 | ... 392 | 393 | def __call__(self, taps: int = 1, **kwargs: Any) -> Any: 394 | kernel: BlurMatrixBase[Any] 395 | 396 | match self: 397 | case BlurMatrix.CIRCLE: 398 | mode = kwargs.pop("mode", ConvMode.SQUARE) 399 | 400 | matrix = [1 for _ in range(((2 * taps + 1) ** (2 if mode == ConvMode.SQUARE else 1)) - 1)] 401 | matrix.insert(len(matrix) // 2, 0) 402 | 403 | return BlurMatrixBase[int](matrix, mode) 404 | 405 | case BlurMatrix.MEAN: 406 | mode = kwargs.pop("mode", ConvMode.SQUARE) 407 | 408 | kernel = BlurMatrixBase[int]([1 for _ in range(((2 * taps + 1)))], mode) 409 | 410 | case BlurMatrix.BINOMIAL: 411 | mode = kwargs.pop("mode", ConvMode.HV) 412 | 413 | c = 1 414 | n = taps * 2 + 1 415 | 416 | matrix = list[int]() 417 | 418 | for i in range(1, taps + 2): 419 | matrix.append(c) 420 | c = c * (n - i) // i 421 | 422 | kernel = BlurMatrixBase(matrix[:-1] + matrix[::-1], mode) 423 | 424 | case BlurMatrix.LOG: 425 | mode = kwargs.pop("mode", ConvMode.HV) 426 | strength = kwargs.get("strength", 100) 427 | 428 | strength = max(1e-6, min(log2(3) * strength / 100, log2(3))) 429 | 430 | weight = 0.5 ** strength / ((1 - 0.5 ** strength) * 0.5) 431 | 432 | matrixf = [1.0] 433 | 434 | for _ in range(taps): 435 | matrixf.append(matrixf[-1] / weight) 436 | 437 | kernel = BlurMatrixBase([*matrixf[::-1], *matrixf[1:]], mode) 438 | 439 | if mode == ConvMode.SQUARE: 440 | kernel = kernel.outer() 441 | 442 | return kernel 443 | -------------------------------------------------------------------------------- /vsrgtools/freqs.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from itertools import count 4 | from typing import Iterable 5 | 6 | from vsexprtools import ExprOp, ExprVars, combine, norm_expr 7 | from vstools import CustomIntEnum, CustomNotImplementedError, FuncExceptT, PlanesT, StrList, flatten_vnodes, vs 8 | 9 | __all__ = [ 10 | 'MeanMode' 11 | ] 12 | 13 | 14 | class MeanMode(CustomIntEnum): 15 | MINIMUM = -2 16 | HARMONIC = -1 17 | GEOMETRIC = 0 18 | 19 | ARITHMETIC = 1 20 | 21 | RMS = 2 22 | CUBIC = 3 23 | MAXIMUM = 4 24 | 25 | LEHMER = 10 26 | 27 | MINIMUM_ABS = 20 28 | MAXIMUM_ABS = 21 29 | 30 | MEDIAN = 30 31 | 32 | def __call__( 33 | self, *_clips: vs.VideoNode | Iterable[vs.VideoNode], planes: PlanesT = None, func: FuncExceptT | None = None 34 | ) -> vs.VideoNode: 35 | func = func or self.__class__ 36 | 37 | clips = flatten_vnodes(_clips) 38 | 39 | n_clips = len(clips) 40 | n_op = n_clips - 1 41 | 42 | if n_clips < 2: 43 | return next(iter(clips)) 44 | 45 | if self == MeanMode.MINIMUM: 46 | return ExprOp.MIN(clips, planes=planes, func=func) 47 | 48 | if self == MeanMode.MAXIMUM: 49 | return ExprOp.MAX(clips, planes=planes, func=func) 50 | 51 | if self == MeanMode.GEOMETRIC: 52 | return combine(clips, ExprOp.MUL, None, None, [1 / n_clips, ExprOp.POW], planes=planes, func=func) 53 | 54 | if self == MeanMode.LEHMER: 55 | counts = range(n_clips) 56 | clip_vars = ExprVars(n_clips) 57 | 58 | expr = StrList([[f'{clip} neutral - D{i}!' for i, clip in zip(counts, clip_vars)]]) 59 | 60 | for y in range(2): 61 | expr.extend([ 62 | [f'D{i}@ {3 - y} pow' for i in counts], 63 | ExprOp.ADD * n_op, f'P{y + 1}!' 64 | ]) 65 | 66 | expr.append('P2@ 0 = 0 P1@ P2@ / ? neutral +') 67 | 68 | return norm_expr(clips, expr, planes=planes, func=func) 69 | 70 | if self in {MeanMode.RMS, MeanMode.ARITHMETIC, MeanMode.CUBIC, MeanMode.HARMONIC}: 71 | return combine( 72 | clips, ExprOp.ADD, f'{self.value} {ExprOp.POW}', None, [ 73 | n_clips, ExprOp.DIV, 1 / self, ExprOp.POW 74 | ], planes=planes, func=func 75 | ) 76 | 77 | if self in {MeanMode.MINIMUM_ABS, MeanMode.MAXIMUM_ABS}: 78 | operator = ExprOp.MIN if self is MeanMode.MINIMUM_ABS else ExprOp.MAX 79 | 80 | expr_string = '' 81 | for src in ExprVars(n_clips): 82 | expr_string += f'{src} neutral - abs {src.upper()}D! ' 83 | 84 | for i, src, srcn in zip(count(), ExprVars(n_clips), ExprVars(1, n_clips)): 85 | expr_string += f'{src.upper()}D@ {srcn.upper()}D@ {operator} {src} ' 86 | 87 | if i == n_clips - 2: 88 | expr_string += f'{srcn} ' 89 | 90 | expr_string += '? ' * n_op 91 | 92 | return norm_expr(clips, expr_string, planes=planes, func=func) 93 | 94 | if self == MeanMode.MEDIAN: 95 | all_clips = str(ExprVars(1, n_clips)) 96 | 97 | n_ops = n_clips - 2 98 | 99 | yzmin, yzmax = [ 100 | all_clips + f' {op}' * n_ops for op in (ExprOp.MIN, ExprOp.MAX) 101 | ] 102 | 103 | return norm_expr( 104 | clips, f'{yzmin} YZMIN! {yzmax} YZMAX! x YZMIN@ min x = YZMIN@ x YZMAX@ max x = YZMAX@ x ? ?', planes 105 | ) 106 | 107 | raise CustomNotImplementedError 108 | -------------------------------------------------------------------------------- /vsrgtools/limit.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from vsexprtools import ExprVars, complexpr_available, norm_expr 4 | from vstools import ( 5 | CustomIndexError, CustomValueError, PlanesT, check_ref_clip, check_variable, core, get_neutral_value, 6 | get_peak_value, normalize_planes, vs 7 | ) 8 | 9 | from .enum import LimitFilterMode 10 | 11 | __all__ = [ 12 | 'limit_filter' 13 | ] 14 | 15 | 16 | def limit_filter( 17 | flt: vs.VideoNode, src: vs.VideoNode, ref: vs.VideoNode | None = None, 18 | mode: LimitFilterMode = LimitFilterMode.CLAMPING, planes: PlanesT = None, 19 | thr: int | tuple[int, int] = 1, elast: float = 2.0, bright_thr: int | None = None 20 | ) -> vs.VideoNode: 21 | assert check_variable(src, limit_filter) 22 | assert check_variable(flt, limit_filter) 23 | check_ref_clip(src, flt, limit_filter) 24 | check_ref_clip(flt, ref, limit_filter) 25 | 26 | if ref is not None: 27 | assert check_variable(ref, limit_filter) 28 | 29 | planes = normalize_planes(flt, planes) 30 | 31 | is_yuv = flt.format.color_family == vs.YUV 32 | 33 | got_ref = ref is not None 34 | 35 | if isinstance(thr, tuple): 36 | thr, thrc = thr 37 | else: 38 | thrc = thr 39 | 40 | if bright_thr is None: 41 | bright_thr = thr 42 | 43 | for var, name, lower_bound in [ 44 | (thr, 'thr', 0), (thrc, 'thrc', 0), (bright_thr, 'bright_thr', 0), (elast, 'elast', 1) 45 | ]: 46 | if var < lower_bound: 47 | raise CustomIndexError(f'{name} must be >= {lower_bound}', limit_filter, reason=var) 48 | 49 | if ref is None and mode != LimitFilterMode.CLAMPING: 50 | raise CustomValueError('You need to specify ref!', limit_filter, reason='mode={mode}', mode=mode) 51 | 52 | force_expr = mode.force_expr 53 | 54 | if any([ 55 | got_ref, flt.format.sample_type == vs.FLOAT, 56 | thr >= 128, bright_thr >= 128, mode != LimitFilterMode.CLAMPING 57 | ]): 58 | force_expr = True 59 | 60 | if thr <= 0 and bright_thr <= 0 and (not is_yuv or thrc <= 0): 61 | return src 62 | 63 | if thr >= 255 and bright_thr >= 255 and (not is_yuv or thrc >= 255): 64 | return flt 65 | 66 | if force_expr: 67 | peak = get_peak_value(flt) 68 | 69 | clips = [flt, src] 70 | 71 | if ref: 72 | clips.append(ref) 73 | 74 | return norm_expr(clips, ( 75 | _limit_filter_expr(got_ref, thr, elast, bright_thr, peak, mode), 76 | _limit_filter_expr(got_ref, thrc, elast, thrc, peak, mode) 77 | )) 78 | 79 | diff = flt.std.MakeDiff(src, planes) 80 | 81 | diff = _limit_filter_lut(diff, elast, thr, bright_thr, [0]) 82 | 83 | if 1 in planes or 2 in planes: 84 | diff = _limit_filter_lut(diff, elast, thrc, thrc, list({*planes} - {0})) 85 | 86 | return flt.std.MakeDiff(diff, planes) 87 | 88 | 89 | def _limit_filter_lut( 90 | diff: vs.VideoNode, elast: float, thr: float, largen_thr: float, planes: list[int] 91 | ) -> vs.VideoNode: 92 | assert check_variable(diff, limit_filter) 93 | 94 | neutral = get_neutral_value(diff) 95 | peak = get_peak_value(diff) 96 | 97 | thr = int(thr * peak / 255) 98 | largen_thr = int(largen_thr * peak / 255) 99 | 100 | if thr >= peak / 2 and largen_thr >= peak / 2: 101 | neutral_clip = diff.std.BlankClip(color=neutral) 102 | 103 | all_planes = list(range(diff.format.num_planes)) 104 | 105 | if planes == all_planes: 106 | return neutral_clip 107 | 108 | diff_planes = planes + list({*all_planes} - {*planes}) 109 | 110 | return core.std.ShufflePlanes( 111 | [neutral_clip, diff], diff_planes, diff.format.color_family 112 | ) 113 | 114 | no_elast = elast <= 1 115 | 116 | def limitLut(x: int) -> int: 117 | dif = x - neutral 118 | 119 | dif_abs = abs(dif) 120 | 121 | thr_1 = largen_thr if dif > 0 else thr 122 | 123 | if dif_abs <= thr_1: 124 | return neutral # type: ignore[return-value] 125 | 126 | if no_elast: 127 | return x 128 | 129 | thr_2 = thr_1 * elast 130 | 131 | if dif_abs >= thr_2: 132 | return x 133 | 134 | thr_slope = 1 / (thr_2 - thr_1) 135 | 136 | return round(dif * (dif_abs - thr_1) * thr_slope + neutral) 137 | 138 | return diff.std.Lut(planes, function=limitLut) 139 | 140 | 141 | def _limit_filter_expr( 142 | got_ref: bool, thr: float, elast: float, largen_thr: float, peak: float, mode: LimitFilterMode 143 | ) -> str: 144 | if mode in {LimitFilterMode.SIMPLE_MIN, LimitFilterMode.SIMPLE_MAX}: 145 | return f'y z - abs y x - abs {mode.op} z x ?' 146 | elif mode in {LimitFilterMode.SIMPLE2_MIN, LimitFilterMode.SIMPLE2_MAX}: 147 | return f'y x - abs z x - abs {mode.op} y z ?' 148 | elif mode in {LimitFilterMode.DIFF_MIN, LimitFilterMode.DIFF_MAX}: 149 | if complexpr_available: 150 | return f'y x - A! y z - B! A@ B@ xor y A@ abs B@ abs {mode.op} x z ? ?' 151 | 152 | return f'y x - y z - xor y y x - abs y z - abs {mode.op} x z ? ?' 153 | 154 | ref = ExprVars[1 + got_ref] 155 | 156 | header = '' 157 | 158 | dif = 'x y -' 159 | dif_abs = f' x {ref} - abs' 160 | 161 | if complexpr_available: 162 | header = f'{dif} DIF! {dif_abs} DIFABS!' 163 | dif, dif_abs = 'DIF@', 'DIFABS@' 164 | 165 | thr, largen_thr = [x * peak / 255 for x in (thr, largen_thr)] 166 | 167 | if thr <= 0 and largen_thr <= 0: 168 | return 'y' 169 | 170 | if thr >= peak and largen_thr >= peak: 171 | return '' 172 | 173 | def _limit_xthr_expr(var: float) -> str: 174 | if var <= 0: 175 | return 'y' 176 | 177 | if var >= peak: 178 | return 'x' 179 | 180 | if elast <= 1: 181 | return f'{dif_abs} {var} <= x y ?' 182 | 183 | thr_1, thr_2 = var, var * elast 184 | thr_slope = 1 / (thr_2 - thr_1) 185 | 186 | return f'{dif_abs} {thr_1} <= x {dif_abs} {thr_2} >= y y {dif} {thr_2} {dif_abs} - * {thr_slope} * + ? ?' 187 | 188 | limitExpr = _limit_xthr_expr(thr) 189 | 190 | if largen_thr != thr: 191 | limitExpr = f'x {ref} > {_limit_xthr_expr(largen_thr)} {limitExpr} ?' 192 | 193 | return f'{header} {limitExpr}' 194 | -------------------------------------------------------------------------------- /vsrgtools/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jaded-Encoding-Thaumaturgy/vs-rgtools/3b18cbfc7306680c60138d7c1e5b461b368805a0/vsrgtools/py.typed -------------------------------------------------------------------------------- /vsrgtools/rgtools.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from vsexprtools import complexpr_available, expr_func 4 | from vstools import NotFoundEnumValue, PlanesT, check_variable, core, normalize_seq, pick_func_stype, vs 5 | 6 | from .aka_expr import ( 7 | aka_removegrain_expr_11_12, aka_removegrain_expr_19, aka_removegrain_expr_20, aka_removegrain_expr_23, 8 | aka_removegrain_expr_24, removegrain_aka_exprs, repair_aka_exprs 9 | ) 10 | from .enum import ( 11 | BlurMatrix, RemoveGrainMode, RemoveGrainModeT, RepairMode, RepairModeT, VerticalCleanerMode, VerticalCleanerModeT 12 | ) 13 | 14 | __all__ = [ 15 | 'repair', 'removegrain', 16 | 'clense', 'backward_clense', 'forward_clense', 17 | 'vertical_cleaner' 18 | ] 19 | 20 | 21 | def repair(clip: vs.VideoNode, repairclip: vs.VideoNode, mode: RepairModeT) -> vs.VideoNode: 22 | assert check_variable(clip, repair) 23 | assert check_variable(repairclip, repair) 24 | 25 | is_float = clip.format.sample_type == vs.FLOAT 26 | mode = normalize_seq(mode, clip.format.num_planes) 27 | 28 | if not sum(mode): 29 | return clip 30 | 31 | if not complexpr_available: 32 | if (RepairMode.CLIP_REF_RG20 in mode or RepairMode.CLIP_REF_RG23 in mode) and is_float: 33 | raise NotFoundEnumValue( 34 | 'Specified RepairMode for rgsf is not implemented!', repair, reason=iter(mode) 35 | ) 36 | 37 | return pick_func_stype(clip, core.rgvs.Repair, core.rgsf.Repair)(clip, repairclip, mode) 38 | 39 | return core.akarin.Expr( 40 | [clip, repairclip], [repair_aka_exprs[m]() for m in mode], clip.format.id, True 41 | ) 42 | 43 | 44 | def removegrain(clip: vs.VideoNode, mode: RemoveGrainModeT) -> vs.VideoNode: 45 | assert check_variable(clip, removegrain) 46 | 47 | mode = normalize_seq(mode, clip.format.num_planes) 48 | mode = list(map(RemoveGrainMode, mode)) 49 | 50 | if not sum(mode): 51 | return clip 52 | 53 | if clip.format.sample_type == vs.INTEGER and all(m in range(24 + 1) for m in mode): 54 | if hasattr(core, "zsmooth"): 55 | return clip.zsmooth.RemoveGrain(mode) 56 | 57 | if hasattr(core, 'rgvs'): 58 | return clip.rgvs.RemoveGrain(mode) 59 | 60 | if not complexpr_available: 61 | return clip.zsmooth.RemoveGrain(mode) 62 | 63 | expr = list[str]() 64 | 65 | for idx, m in enumerate(mode): 66 | if m == RemoveGrainMode.BINOMIAL_BLUR: 67 | if all(mm == m for mm in mode): 68 | return BlurMatrix.BINOMIAL()(clip) 69 | expr.append(aka_removegrain_expr_11_12()) 70 | 71 | elif RemoveGrainMode.BOB_TOP_CLOSE <= m <= RemoveGrainMode.BOB_BOTTOM_INTER: 72 | return pick_func_stype(clip, core.lazy.rgvs.RemoveGrain, core.lazy.zsmooth.RemoveGrain)(clip, mode) 73 | 74 | elif m == RemoveGrainMode.BOX_BLUR_NO_CENTER: 75 | if set(mode) == {RemoveGrainMode.BOX_BLUR_NO_CENTER}: 76 | return BlurMatrix.CIRCLE()(clip) 77 | expr.append(aka_removegrain_expr_19()) 78 | 79 | elif m == RemoveGrainMode.BOX_BLUR: 80 | if set(mode) == {RemoveGrainMode.BOX_BLUR}: 81 | return BlurMatrix.MEAN()(clip) 82 | expr.append(aka_removegrain_expr_20()) 83 | 84 | elif m == RemoveGrainMode.EDGE_DEHALO: 85 | expr.append(aka_removegrain_expr_23(0 if idx == 0 else -0.5)) 86 | 87 | elif m == RemoveGrainMode.EDGE_DEHALO2: 88 | expr.append(aka_removegrain_expr_24(0 if idx == 0 else -0.5)) 89 | 90 | else: 91 | expr.append(removegrain_aka_exprs[m]()) 92 | 93 | return expr_func(clip, expr, opt=True) 94 | 95 | 96 | def clense( 97 | clip: vs.VideoNode, 98 | previous_clip: vs.VideoNode | None = None, next_clip: vs.VideoNode | None = None, 99 | planes: PlanesT = None 100 | ) -> vs.VideoNode: 101 | return pick_func_stype(clip, core.lazy.rgvs.Clense, core.lazy.rgsf.Clense)(clip, previous_clip, next_clip, planes) 102 | 103 | 104 | def forward_clense(clip: vs.VideoNode, planes: PlanesT = None) -> vs.VideoNode: 105 | return pick_func_stype(clip, core.lazy.rgvs.ForwardClense, core.lazy.rgsf.ForwardClense)(clip, planes) 106 | 107 | 108 | def backward_clense(clip: vs.VideoNode, planes: PlanesT = None) -> vs.VideoNode: 109 | return pick_func_stype(clip, core.lazy.rgvs.BackwardClense, core.lazy.rgsf.BackwardClense)(clip, planes) 110 | 111 | 112 | def vertical_cleaner(clip: vs.VideoNode, mode: VerticalCleanerModeT = VerticalCleanerMode.MEDIAN) -> vs.VideoNode: 113 | return pick_func_stype(clip, core.lazy.rgvs.VerticalCleaner, core.lazy.rgsf.VerticalCleaner)(clip, mode) 114 | -------------------------------------------------------------------------------- /vsrgtools/sharp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | from vsexprtools import norm_expr 6 | from vstools import ( 7 | CustomTypeError, PlanesT, VSFunction, check_ref_clip, check_variable, FunctionUtil, normalize_planes, vs, ConvMode 8 | ) 9 | 10 | from .blur import gauss_blur, min_blur, box_blur, median_blur 11 | from .enum import BlurMatrix 12 | from .limit import limit_filter 13 | from .rgtools import repair 14 | from .util import normalize_radius 15 | 16 | __all__ = [ 17 | 'unsharpen', 18 | 'unsharp_masked', 19 | 'limit_usm', 20 | 'fine_sharp', 21 | 'soothe' 22 | ] 23 | 24 | 25 | def unsharpen( 26 | clip: vs.VideoNode, strength: float = 1.0, sigma: float | list[float] = 1.5, 27 | prefilter: vs.VideoNode | VSFunction | None = None, **kwargs: Any 28 | ) -> vs.VideoNode: 29 | assert check_variable(clip, unsharpen) 30 | 31 | ref = prefilter(clip) if callable(prefilter) else prefilter 32 | 33 | check_ref_clip(clip, ref) 34 | 35 | den = ref or clip 36 | blur = gauss_blur(den, sigma, **kwargs) 37 | 38 | unsharp = norm_expr([den, blur], f'x y - {strength} * x +', 0) 39 | 40 | if ref is not None: 41 | return unsharp 42 | 43 | return unsharp.std.MergeDiff(clip.std.MakeDiff(den)) 44 | 45 | 46 | def unsharp_masked( 47 | clip: vs.VideoNode, radius: int | list[int] = 1, strength: float = 100.0, planes: PlanesT = None 48 | ) -> vs.VideoNode: 49 | planes = normalize_planes(clip, planes) 50 | 51 | if isinstance(radius, list): 52 | return normalize_radius(clip, unsharp_masked, radius, planes, strength=strength) 53 | 54 | blurred = BlurMatrix.LOG(radius, strength=strength)(clip, planes) 55 | 56 | return norm_expr([clip, blurred], 'x dup y - +') 57 | 58 | 59 | def limit_usm( 60 | clip: vs.VideoNode, blur: int | vs.VideoNode | VSFunction = 1, thr: int | tuple[int, int] = 3, 61 | elast: float = 4.0, bright_thr: int | None = None, planes: PlanesT = None 62 | ) -> vs.VideoNode: 63 | """Limited unsharp_masked.""" 64 | 65 | if callable(blur): 66 | blurred = blur(clip) 67 | elif isinstance(blur, vs.VideoNode): 68 | blurred = blur 69 | elif blur <= 0: 70 | blurred = min_blur(clip, -blur, planes=planes) 71 | elif blur == 1: 72 | blurred = BlurMatrix.BINOMIAL()(clip, planes) 73 | elif blur == 2: 74 | blurred = BlurMatrix.MEAN()(clip, planes) 75 | else: 76 | raise CustomTypeError("'blur' must be an int, clip or a blurring function!", limit_usm, blur) 77 | 78 | sharp = norm_expr([clip, blurred], 'x dup y - +', planes) 79 | 80 | return limit_filter(sharp, clip, thr=thr, elast=elast, bright_thr=bright_thr) 81 | 82 | 83 | def fine_sharp( 84 | clip: vs.VideoNode, mode: int = 1, sstr: float = 2.0, cstr: float | None = None, xstr: float = 0.19, 85 | lstr: float = 1.49, pstr: float = 1.272, ldmp: float | None = None, planes: PlanesT = 0 86 | ) -> vs.VideoNode: 87 | from scipy import interpolate 88 | 89 | func = FunctionUtil(clip, fine_sharp, planes) 90 | 91 | if cstr is None: 92 | cstr = interpolate.CubicSpline( 93 | x=(0, 0.5, 1.0, 2.0, 2.5, 3.0, 3.5, 4.0, 8.0, 255.0), 94 | y=(0, 0.1, 0.6, 0.9, 1.0, 1.09, 1.15, 1.19, 1.249, 1.5) 95 | )(sstr) 96 | 97 | if ldmp is None: 98 | ldmp = sstr + 0.1 99 | 100 | blur_kernel = BlurMatrix.BINOMIAL() 101 | blur_kernel2 = blur_kernel 102 | 103 | if mode < 0: 104 | cstr **= 0.8 105 | blur_kernel2 = box_blur 106 | 107 | mode = abs(mode) 108 | 109 | if mode == 1: 110 | blurred = median_blur(blur_kernel(func.work_clip)) 111 | elif mode > 1: 112 | blurred = blur_kernel(median_blur(func.work_clip)) 113 | if mode == 3: 114 | blurred = median_blur(blurred) 115 | 116 | diff = norm_expr( 117 | [func.work_clip, blurred], 118 | 'range_size 256 / SCL! x y - SCL@ / D! D@ abs DA! DA@ {lstr} / 1 {pstr} / pow {sstr} * ' 119 | 'D@ DA@ 0.001 + / * D@ 2 pow D@ 2 pow {ldmp} + / * SCL@ * neutral +', 120 | lstr=lstr, pstr=pstr, sstr=sstr, ldmp=ldmp 121 | ) 122 | 123 | sharp = func.work_clip 124 | 125 | if sstr: 126 | sharp = sharp.std.MergeDiff(diff) 127 | 128 | if cstr: 129 | diff = norm_expr(diff, 'x neutral - {cstr} * neutral +', cstr=cstr) 130 | diff = blur_kernel2(diff) 131 | sharp = sharp.std.MakeDiff(diff) 132 | 133 | if xstr: 134 | xysharp = norm_expr([sharp, box_blur(sharp)], 'x x y - 9.9 * +') 135 | rpsharp = repair(xysharp, sharp, 12) 136 | sharp = rpsharp.std.Merge(sharp, weight=[1 - xstr]) 137 | 138 | return func.return_clip(sharp) 139 | 140 | 141 | def soothe( 142 | flt: vs.VideoNode, src: vs.VideoNode, spatial_strength: int = 0, temporal_strength: int = 25, 143 | spatial_radius: int = 1, temporal_radius: int = 1, scenechange: bool = False, planes: PlanesT = 0 144 | ) -> vs.VideoNode: 145 | sharp_diff = src.std.MakeDiff(flt, planes) 146 | 147 | expr = ( 148 | 'x neutral - X! y neutral - Y! X@ 0 < Y@ 0 < xor X@ 100 / {strength} * ' 149 | 'X@ abs Y@ abs > X@ {strength} * Y@ 100 {strength} - * + 100 / X@ ? ? neutral +' 150 | ) 151 | 152 | if spatial_strength: 153 | soothe = box_blur(sharp_diff, radius=spatial_radius, planes=planes) 154 | strength = 100 - abs(max(min(spatial_strength, 100), 0)) 155 | sharp_diff = norm_expr([sharp_diff, soothe], expr, strength=strength, planes=planes) 156 | 157 | if temporal_strength: 158 | soothe = ( 159 | BlurMatrix.MEAN(temporal_radius, mode=ConvMode.TEMPORAL) 160 | (sharp_diff, planes=planes, scenechange=scenechange) 161 | ) 162 | strength = 100 - abs(max(min(temporal_strength, 100), -100)) 163 | sharp_diff = norm_expr([sharp_diff, soothe], expr, strength=strength, planes=planes) 164 | 165 | return src.std.MakeDiff(sharp_diff, planes) 166 | -------------------------------------------------------------------------------- /vsrgtools/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Sequence, TypeVar, cast 4 | 5 | from vstools import ( 6 | ConvMode, GenericVSFunction, KwargsT, Nb, PlanesT, check_variable, check_variable_format, join, normalize_planes, 7 | normalize_seq, plane, vs 8 | ) 9 | 10 | from .enum import RemoveGrainMode, RepairMode, BlurMatrix 11 | 12 | __all__ = [ 13 | 'wmean_matrix', 'mean_matrix', 14 | 'norm_rmode_planes', 15 | 'normalize_radius' 16 | ] 17 | 18 | wmean_matrix = list(BlurMatrix.BINOMIAL(1, mode=ConvMode.SQUARE)) 19 | mean_matrix = list(BlurMatrix.MEAN(1, mode=ConvMode.SQUARE)) 20 | 21 | RModeT = TypeVar('RModeT', RemoveGrainMode, RepairMode) 22 | 23 | 24 | def norm_rmode_planes( 25 | clip: vs.VideoNode, mode: int | RModeT | Sequence[int | RModeT], planes: PlanesT = None 26 | ) -> list[int]: 27 | assert check_variable(clip, norm_rmode_planes) 28 | 29 | modes_array = normalize_seq(mode, clip.format.num_planes) 30 | 31 | planes = normalize_planes(clip, planes) 32 | 33 | return [ 34 | cast(RModeT, rep if i in planes else 0) for i, rep in enumerate(modes_array, 0) 35 | ] 36 | 37 | 38 | def normalize_radius( 39 | clip: vs.VideoNode, func: GenericVSFunction, radius: list[Nb] | tuple[str, list[Nb]], 40 | planes: list[int], **kwargs: Any 41 | ) -> vs.VideoNode: 42 | assert check_variable_format(clip, normalize_radius) 43 | 44 | name, radius = radius if isinstance(radius, tuple) else ('radius', radius) 45 | 46 | radius = normalize_seq(radius, clip.format.num_planes) 47 | 48 | def _get_kwargs(rad: Nb) -> KwargsT: 49 | return kwargs | {name: rad, 'planes': planes} 50 | 51 | if len(set(radius)) > 0: 52 | if len(planes) != 1: 53 | return join([ 54 | func(plane(clip, i), **_get_kwargs(rad)) for i, rad in enumerate(radius) 55 | ]) 56 | 57 | radius_i = radius[planes[0]] 58 | else: 59 | radius_i = radius[0] 60 | 61 | return func(clip, **_get_kwargs(radius_i)) 62 | --------------------------------------------------------------------------------