Metadata-Version: 2.4
Name: vapoursynth-knlmeansvk
Version: 2.0
Summary: Non-local means de-noising on the VapourSynth Vulkan GPU device
Author-Email: Edoardo Brunetti <edo.brunetti@gmail.com>
Maintainer-Email: Jaded Encoding Thaumaturgy <jaded.encoding.thaumaturgy@gmail.com>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Project-URL: Source Code, https://github.com/Jaded-Encoding-Thaumaturgy/vapoursynth-knlmeansvk
Project-URL: Bug Tracker, https://github.com/Jaded-Encoding-Thaumaturgy/vapoursynth-knlmeansvk/issues
Project-URL: Changelog, https://github.com/Jaded-Encoding-Thaumaturgy/vapoursynth-knlmeansvk/blob/master/CHANGELOG
Project-URL: Contact, https://discord.gg/XTpc6Fa9eB
Requires-Python: >=3.12
Requires-Dist: vapoursynth>=80
Description-Content-Type: text/markdown

# KNLMeansVk

**KNLMeansVk** is a Non-local means de-noising filter for VapourSynth, running on the
core's Vulkan device. It is a port of Khanattila's OpenCL implementation, **KNLMeansCL**,
and keeps its algorithm exactly: the GPU-friendly NL-means decomposition proposed by
Goossens et al. with per-offset patch distances, a separable box filter and symmetric
pair accumulation.

The NLMeans filter, originally proposed by Buades et al., is a very popular filter for the
removal of white Gaussian noise, due to its simplicity and excellent performance. The
strength of this algorithm is to exploit the repetitive character of the image in order to
de-noise the image, unlike conventional de-noising algorithms which typically operate in a
local neighbourhood.

Frames stay in video memory for the whole chain: the filter takes and produces GPU
resident clips, composes with every other GPU filter without crossing the PCIe bus, and a
CPU clip passed to it is uploaded automatically.

## Requirements

- VapourSynth R80 or later, with a GPU and driver that support Vulkan 1.4. Check with
  `core.vulkan_devices` from Python.
- Works on Linux, Windows and macOS (the latter through MoltenVK).

## Installation

```bash
pip install vapoursynth-knlmeansvk
```

The wheel installs the plugin into VapourSynth's Python package plugin directory, where
it autoloads. Wheels exist for Windows (x64), Linux (x86_64 and aarch64) and macOS (Intel
and Apple silicon).

## Usage

```python3
knlmvk.KNLMeans(clip clip[, int d=1, int a=2, int s=4, float h=1.2, string channels="AUTO", int wmode=0, float wref=1.0, clip rclip=None])
```

- **clip**: the input clip. Gray, YUV or RGB; 8-16 bit integer, half or single precision
  float. Frame properties are copied from it.
- **d**: temporal radius, in `[0, 64]`; frames `[n-d, n+d]` take part in the search.
  `d=0` is purely spatial.
- **a**: spatial search radius, in `[1, 128]`; the search window is
  `(2a+1)x(2a+1)x(2d+1)`.
- **s**: similarity neighbourhood (patch) radius, in `[0, 8]`; patches are
  `(2s+1)x(2s+1)`.
- **h**: filtering strength, on a 0-255 scale regardless of bit depth. Larger values
  smooth more.
- **channels**: which channels drive the similarity and are filtered.
  - `"AUTO"`: `"RGB"` for RGB clips, `"Y"` otherwise.
  - `"Y"`: luma only; chroma planes pass through untouched.
  - `"UV"`: chroma jointly, at chroma resolution; luma passes through.
  - `"YUV"`: all three jointly; requires 4:4:4.
  - `"RGB"`: all three jointly, with brightness-adaptive red/blue weighting.
- **wmode**: weighting kernel. `0` Welsch (smooth, never zero), `1`/`2`/`3` bisquare
  variants that cut off hard and therefore need a considerably larger `h`.
- **wref**: weight of the pixel being filtered, as a multiple of the largest neighbour
  weight.
- **rclip**: a reference clip driving the block matching (weights are computed on it, the
  average is taken over `clip`). Must match `clip` in format, dimensions and length.

See the upstream **[Wiki](https://github.com/Khanattila/KNLMeansCL/wiki)** for parameter
guidance; everything about `d`, `a`, `s`, `h`, `channels`, `wmode`, `wref` and `rclip`
carries over.

## Differences from KNLMeansCL

- `device_type`, `device_id`, `ocl_x`, `ocl_y` and `ocl_r` are gone: the core owns the
  Vulkan device (`core.set_vulkan_device()` selects one) and the port sizes its own
  dispatches. `info` is gone with the OpenCL platform text it printed, and
  `mode_9_to_15bits` is gone because the OpenCL image-format modes it toggled between do
  not exist here; there is one normalize-by-maximum path for every integer depth.
- The plugin namespace is `knlmvk` and the function `KNLMeans`, replacing
  `knlm.KNLMeansCL`.
- The filter runs `fmParallel` with per-frame GPU scratch instead of serializing frames
  through one command queue, so concurrent frames actually overlap. The price is that
  every frame in flight holds its own scratch: `(2d+1)` float planes per filtered channel
  (twice that with `rclip`) plus about a dozen more, roughly 1 GB per frame at 4K RGB with
  `d=3`. The core runs up to one frame per thread, so on a card that runs out of memory
  lower `core.num_threads` or `d` rather than expecting the filter to slow down.
- At the last `d` frames of a clip the OpenCL implementation read stale stack layers for
  the future temporal side. Here the temporal window shrinks symmetrically at both clip
  ends instead, mirroring its start-of-clip behaviour; every interior frame is
  numerically equivalent to the original.
- No 8192x8192 resolution limit (it came from OpenCL image dimensions).
- AviSynth support was removed together with OpenCL: the port is built on VapourSynth's
  GPU API.
- The `ocl_x`/`ocl_y`/`ocl_r` tuning knobs are gone. The three kernels the OpenCL
  implementation used per search offset — pixel distance, horizontal box, vertical box —
  are one fused kernel here whose intermediates stay in shared memory, each thread emits
  several output rows, and several search offsets share a dispatch. `KNLMVK_BATCH` (1-8,
  offsets per round) and `KNLMVK_ROWS` (1-8, output rows per thread) retune the last two
  on hardware unlike the one they were measured on.

## Compilation

Requires a C++20 compiler, meson, ninja, the VapourSynth headers (R80+, provided by the
`vapoursynth` Python package) and the Vulkan headers. Only the headers are needed — the
plugin does not link the Vulkan loader, because the core hands it every entry point
already resolved.

```bash
meson setup build
meson compile -C build
meson install -C build
```

If the headers are not found automatically, pass
`-Dvapoursynth_include=/path/to/vapoursynth/include` and/or
`-Dvulkan_include=/path/to/vulkan/include`.

`meson install` puts the plugin in the `vapoursynth` Python package's plugin directory;
`-Dplugindir=/path` sends it somewhere else, such as `lib/vapoursynth` of a system
VapourSynth. The compiled plugin (`libknlmvulkan.so`, `knlmvulkan.dll` or
`libknlmvulkan.dylib`) also sits in the build directory for copying by hand. The compute
kernel is embedded with C23 `#embed` where the compiler supports it (clang 19+, gcc 15+)
and through a meson-generated header otherwise, so MSVC builds without a separate step.

Wheels come from the same meson build through
[meson-python](https://mesonbuild.com/meson-python/): `uv build` or
`python -m build`, with meson options passed as `-Csetup-args=-Doption=value`.

## License

This project is licensed under the GNU General Public License v3.0 or later (GPLv3+),
like the KNLMeansCL implementation it is ported from, Copyright© 2015-2020
Edoardo Brunetti.

The fused weight kernel follows the structure of the CUDA NLMeans in
[vapoursynth-zipcu](https://github.com/dnjulek/vapoursynth-zipcu) by dnjulek (MIT).
