Skip to content

Use the building blocks

The presets cover the jobs the original paper covers. For anything else — a different filter, an extra step, one stage on its own — the parts are available separately, and they are the same parts the pipelines are built from.

The parts

Operation What it does
bgr_u8_to_ntsc / ntsc_to_bgr_u8 Convert 8-bit colour to a brightness-plus-colour form, and back
blur_dn Blur and halve the resolution, repeatedly
build_lpyr / recon_lpyr Split an image into detail at each scale, and reconstruct it
ideal_bandpass Keep a band of frequencies, using a Fourier transform
butter_bandpass Keep a band with a Butterworth filter, running forward in time
iir_bandpass Keep a band by subtracting two running averages
apply_gain Scale the three channels — brightness and two colour — separately

Same names and argument order on every backend, so a chain written against one runs on another. Full signatures are in the building blocks reference.

On the processor

import numpy as np
from vidmag.cpu import ops

frames = np.zeros((60, 64, 64, 3), dtype=np.uint8)  # your frames, 8-bit BGR

ntsc = ops.bgr_u8_to_ntsc(frames)
small = ops.blur_dn(ntsc, 2)
band = ops.iir_bandpass(small, 0.4, 0.05)
amplified = ops.apply_gain(band, 20.0, 2.0, 2.0)
print(amplified.shape)

On an NVIDIA graphics processor, without copying between steps

The GPU versions take and return a DeviceArray, which stays on the card. Nothing is copied back until you ask:

import numpy as np
from vidmag.cuda import ops
from vidmag.cuda.array import DeviceArray

frames = DeviceArray.from_numpy(np.zeros((60, 64, 64, 3), dtype=np.uint8))

ntsc = ops.bgr_u8_to_ntsc(frames)
small = ops.blur_dn(ntsc, 2)
band = ops.iir_bandpass(small, 0.4, 0.05)
result = band.numpy()  # the one copy back

Handing the result to PyTorch without copying

A DeviceArray implements the protocol array libraries use to share memory, so a result can go straight into a tensor — no copy, and the two then refer to the same memory:

import torch  # doctest: +SKIP

tensor = torch.from_dlpack(band)  # doctest: +SKIP
assert tensor.data_ptr() == band.ptr  # doctest: +SKIP

This is what makes the library usable as one stage inside a larger pipeline already living on the graphics processor.

A pipeline that runs on every backend

The four pipelines are written once in terms of the operations above, and each backend supplies its own versions. Do the same, and your code runs wherever the library does:

import numpy as np
from vidmag.backend import registry

frames = np.zeros((60, 64, 64, 3), dtype=np.uint8)  # your frames, 8-bit BGR

name, impl = registry.select("auto")
print("running on", name)
out = impl.motion_lpyr_iir_core(
    frames, 30.0, alpha=10.0, lambda_c=16.0, r1=0.4, r2=0.05
)
print(out.shape)

Adding support for new hardware

Implement the operations above for the hardware, then:

from vidmag.backend import generic, registry

backend = generic.bind(my_operations)  # all four pipelines, derived

That is the whole job. The pipelines follow from the operations, and the conformance tests compare them against the NumPy reference. See backends and hardware.